summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp26
-rw-r--r--apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java2
-rw-r--r--api/current.txt155
-rw-r--r--api/system-current.txt279
-rw-r--r--api/test-current.txt181
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java46
-rw-r--r--cmds/idmap2/idmap2/Create.cpp8
-rw-r--r--cmds/idmap2/idmap2d/Idmap2Service.cpp20
-rw-r--r--cmds/idmap2/include/idmap2/FileUtils.h5
-rw-r--r--cmds/idmap2/libidmap2/FileUtils.cpp30
-rw-r--r--cmds/idmap2/tests/FileUtilsTests.cpp23
-rw-r--r--cmds/idmap2/tests/Idmap2BinaryTests.cpp23
-rw-r--r--cmds/statsd/src/StatsService.cpp19
-rw-r--r--cmds/statsd/src/StatsService.h11
-rw-r--r--cmds/statsd/src/atoms.proto306
-rw-r--r--cmds/statsd/src/config/ConfigManager.cpp28
-rw-r--r--cmds/statsd/src/config/ConfigManager.h23
-rw-r--r--cmds/statsd/src/external/StatsPullerManager.cpp12
-rw-r--r--config/hiddenapi-greylist.txt1
-rw-r--r--core/java/android/app/ActivityThread.java14
-rw-r--r--core/java/android/app/ActivityView.java63
-rw-r--r--core/java/android/app/AppOpsManager.java49
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl5
-rw-r--r--core/java/android/app/IApplicationThread.aidl3
-rw-r--r--core/java/android/app/KeyguardManager.java19
-rw-r--r--core/java/android/app/Notification.java106
-rw-r--r--core/java/android/app/NotificationManager.java3
-rw-r--r--core/java/android/app/StatsManager.java42
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java110
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/contentsuggestions/ClassificationsRequest.java11
-rw-r--r--core/java/android/app/contentsuggestions/ContentClassification.java13
-rw-r--r--core/java/android/app/contentsuggestions/ContentSelection.java13
-rw-r--r--core/java/android/app/contentsuggestions/SelectionsRequest.java20
-rw-r--r--core/java/android/app/role/RoleManager.java9
-rw-r--r--core/java/android/app/usage/IUsageStatsManager.aidl3
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java82
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java37
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java4
-rw-r--r--core/java/android/bluetooth/BluetoothCodecStatus.java5
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java86
-rw-r--r--core/java/android/bluetooth/BluetoothProfile.java13
-rw-r--r--core/java/android/content/ContentResolver.java1
-rw-r--r--core/java/android/content/Intent.java27
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java5
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl4
-rw-r--r--core/java/android/content/pm/IShortcutService.aidl2
-rw-r--r--core/java/android/content/pm/LauncherApps.aidl19
-rw-r--r--core/java/android/content/pm/LauncherApps.java103
-rw-r--r--core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java6
-rw-r--r--core/java/android/content/pm/PackageBackwardCompatibility.java25
-rw-r--r--core/java/android/content/pm/PackageInstaller.java22
-rw-r--r--core/java/android/content/pm/PackageParser.java1
-rw-r--r--core/java/android/content/pm/ShortcutManager.java17
-rw-r--r--core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl1
-rw-r--r--core/java/android/content/rollback/IRollbackManager.aidl6
-rw-r--r--core/java/android/content/rollback/RollbackInfo.java24
-rw-r--r--core/java/android/content/rollback/RollbackManager.java67
-rw-r--r--core/java/android/database/AbstractCursor.java49
-rw-r--r--core/java/android/database/Cursor.java42
-rw-r--r--core/java/android/database/CursorWrapper.java12
-rw-r--r--core/java/android/hardware/biometrics/BiometricFaceConstants.java45
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java34
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java25
-rw-r--r--core/java/android/hardware/biometrics/IBiometricService.aidl8
-rw-r--r--core/java/android/hardware/display/ColorDisplayManager.java396
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java20
-rw-r--r--core/java/android/hardware/display/IColorDisplayManager.aidl17
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl5
-rw-r--r--core/java/android/hardware/display/Time.aidl19
-rw-r--r--core/java/android/hardware/display/Time.java77
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java79
-rw-r--r--core/java/android/hardware/hdmi/HdmiSwitchClient.java128
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java26
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java245
-rw-r--r--core/java/android/net/DhcpResults.java158
-rw-r--r--core/java/android/net/ProxyInfo.java57
-rw-r--r--core/java/android/net/StaticIpConfiguration.java66
-rw-r--r--core/java/android/net/VpnService.java9
-rw-r--r--core/java/android/net/apf/ApfCapabilities.java5
-rw-r--r--core/java/android/net/captiveportal/CaptivePortalProbeResult.java4
-rw-r--r--core/java/android/net/captiveportal/CaptivePortalProbeSpec.java16
-rw-r--r--core/java/android/os/BatteryManager.java24
-rw-r--r--core/java/android/os/BatteryStats.java4
-rw-r--r--core/java/android/os/BugreportManager.java105
-rw-r--r--core/java/android/os/ExternalVibration.aidl19
-rw-r--r--core/java/android/os/ExternalVibration.java171
-rw-r--r--core/java/android/os/FileUtils.java2
-rw-r--r--core/java/android/os/GraphicsEnvironment.java69
-rw-r--r--core/java/android/os/IExternalVibrationController.aidl45
-rw-r--r--core/java/android/os/IExternalVibratorService.aidl63
-rw-r--r--core/java/android/os/IStatsManager.aidl17
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java34
-rw-r--r--core/java/android/os/PowerManager.java7
-rw-r--r--core/java/android/os/VibrationEffect.java42
-rw-r--r--core/java/android/permission/IPermissionController.aidl2
-rw-r--r--core/java/android/permission/PermissionControllerManager.java77
-rw-r--r--core/java/android/permission/PermissionControllerService.java40
-rw-r--r--core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java20
-rw-r--r--core/java/android/provider/CalendarContract.java21
-rw-r--r--core/java/android/provider/ContactsContract.java2
-rw-r--r--core/java/android/provider/DeviceConfig.java45
-rw-r--r--core/java/android/provider/MediaStore.java7
-rw-r--r--core/java/android/provider/Settings.java91
-rw-r--r--core/java/android/provider/VoicemailContract.java2
-rw-r--r--core/java/android/service/notification/Adjustment.java9
-rw-r--r--core/java/android/service/notification/NotificationAssistantService.java8
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java6
-rw-r--r--core/java/android/service/notification/NotificationStats.java9
-rw-r--r--core/java/android/util/FeatureFlagUtils.java10
-rw-r--r--core/java/android/view/Display.java19
-rw-r--r--core/java/android/view/InsetsController.java150
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java7
-rw-r--r--core/java/android/view/InsetsState.java25
-rw-r--r--core/java/android/view/RemoteAnimationAdapter.java19
-rw-r--r--core/java/android/view/SurfaceControl.java98
-rw-r--r--core/java/android/view/View.java90
-rw-r--r--core/java/android/view/WindowInsets.java88
-rw-r--r--core/java/android/view/WindowInsetsController.java53
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java20
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java2
-rw-r--r--core/java/android/view/textclassifier/ActionsSuggestionsHelper.java13
-rw-r--r--core/java/android/view/textclassifier/ConversationActions.java12
-rw-r--r--core/java/android/view/textclassifier/TextClassificationManager.java17
-rw-r--r--core/java/android/view/textclassifier/TextClassifierEvent.java63
-rw-r--r--core/java/android/view/textclassifier/TextClassifierEventTronLogger.java60
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java12
-rw-r--r--core/java/android/webkit/WebViewZygote.java56
-rw-r--r--core/java/com/android/internal/app/AssistUtils.java43
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java134
-rw-r--r--core/java/com/android/internal/app/ColorDisplayController.java352
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl3
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java1
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java35
-rw-r--r--core/java/com/android/internal/net/VpnConfig.java5
-rw-r--r--core/java/com/android/internal/net/VpnInfo.java10
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java26
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java2
-rw-r--r--core/java/com/android/internal/os/WebViewZygoteInit.java47
-rw-r--r--core/java/com/android/internal/os/ZygoteArguments.java2
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl2
-rwxr-xr-xcore/jni/android/graphics/Bitmap.cpp16
-rw-r--r--core/jni/android/graphics/Paint.cpp132
-rw-r--r--core/jni/android/graphics/PaintFilter.cpp62
l---------[-rw-r--r--]core/jni/android_media_MediaMetricsJNI.cpp91
l---------[-rw-r--r--]core/jni/android_media_MediaMetricsJNI.h34
-rw-r--r--core/jni/android_view_SurfaceControl.cpp40
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp3
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp8
-rw-r--r--core/jni/fd_utils.cpp14
-rw-r--r--core/proto/android/app/settings_enums.proto6
-rw-r--r--core/proto/android/hardware/biometrics/enums.proto46
-rw-r--r--core/proto/android/providers/settings/global.proto4
-rw-r--r--core/proto/android/providers/settings/secure.proto4
-rw-r--r--core/proto/android/server/jobscheduler.proto82
-rw-r--r--core/proto/android/service/usb.proto17
-rw-r--r--core/proto/android/stats/devicepolicy/device_policy_enums.proto3
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--core/res/res/values/config.xml12
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--core/res/res/values/strings.xml3
-rw-r--r--core/res/res/values/symbols.xml9
-rw-r--r--core/tests/coretests/Android.mk1
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java3
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java42
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageParserTest.java2
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java5
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java70
-rw-r--r--core/tests/coretests/src/android/view/WindowInsetsTest.java26
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java14
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java7
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java17
-rw-r--r--graphics/java/android/graphics/Bitmap.java25
-rw-r--r--graphics/java/android/graphics/ColorSpace.java4
-rw-r--r--libs/androidfw/AssetManager2.cpp20
-rw-r--r--libs/androidfw/ResourceUtils.cpp10
-rw-r--r--libs/androidfw/include/androidfw/ResourceUtils.h11
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp10
-rw-r--r--libs/hwui/DeviceInfo.cpp9
-rw-r--r--libs/hwui/DeviceInfo.h2
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp18
-rw-r--r--libs/hwui/SkiaCanvas.cpp14
-rw-r--r--libs/hwui/hwui/Bitmap.cpp9
-rw-r--r--libs/hwui/hwui/Bitmap.h1
-rw-r--r--libs/hwui/hwui/Canvas.cpp38
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp78
-rw-r--r--libs/hwui/hwui/MinikinSkia.h10
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp9
-rw-r--r--libs/hwui/hwui/MinikinUtils.h18
-rw-r--r--libs/hwui/hwui/Paint.h40
-rw-r--r--libs/hwui/hwui/PaintFilter.h1
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp144
-rw-r--r--libs/hwui/pipeline/skia/GLFunctorDrawable.cpp1
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.cpp4
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h1
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp18
-rw-r--r--libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp5
-rw-r--r--libs/hwui/private/hwui/DrawGlInfo.h5
-rw-r--r--libs/hwui/renderthread/EglManager.cpp28
-rw-r--r--libs/hwui/renderthread/EglManager.h2
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp13
-rw-r--r--libs/hwui/renderthread/VulkanManager.h6
-rw-r--r--libs/hwui/tests/common/TestUtils.cpp13
-rw-r--r--libs/hwui/tests/common/TestUtils.h4
-rw-r--r--libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp2
-rw-r--r--libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp3
-rw-r--r--libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp6
-rw-r--r--libs/hwui/tests/common/scenes/ListViewAnimation.cpp7
-rw-r--r--libs/hwui/tests/common/scenes/MagnifierAnimation.cpp5
-rw-r--r--libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp4
-rw-r--r--libs/hwui/tests/common/scenes/TextAnimation.cpp5
-rw-r--r--libs/hwui/tests/common/scenes/TvApp.cpp7
-rw-r--r--libs/hwui/utils/Color.cpp14
-rw-r--r--libs/hwui/utils/Color.h1
-rw-r--r--media/Android.bp1
-rw-r--r--media/apex/java/android/media/MediaConstants.java3
-rw-r--r--media/apex/java/android/media/MediaController2.java14
-rw-r--r--media/apex/java/android/media/MediaSession2.java15
-rw-r--r--media/apex/java/android/media/MediaSession2Service.java8
-rw-r--r--media/java/android/media/AudioManager.java31
-rw-r--r--media/java/android/media/AudioSystem.java13
-rw-r--r--media/java/android/media/IAudioService.aidl2
-rw-r--r--media/java/android/media/MediaScanner.java10
-rw-r--r--media/java/android/media/Session2Token.java (renamed from media/apex/java/android/media/Session2Token.java)122
-rw-r--r--media/java/android/media/session/ISessionManager.aidl1
-rw-r--r--media/java/android/media/session/MediaSessionManager.java35
-rw-r--r--media/jni/Android.bp2
-rw-r--r--media/jni/android_media_ImageReader.cpp5
-rw-r--r--media/jni/android_media_MediaMetricsJNI.cpp142
-rw-r--r--media/jni/android_media_MediaMetricsJNI.h1
-rw-r--r--media/jni/android_media_MediaPlayer2.cpp15
-rw-r--r--native/android/surface_control.cpp25
-rw-r--r--native/webview/plat_support/draw_fn.h13
-rw-r--r--native/webview/plat_support/draw_functor.cpp14
-rw-r--r--packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java11
-rw-r--r--packages/CarSystemUI/Android.bp2
-rw-r--r--packages/CarSystemUI/res/layout/car_navigation_bar.xml3
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java13
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java8
-rw-r--r--packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java2
-rw-r--r--packages/NetworkStack/src/android/net/ip/IpClient.java5
-rw-r--r--packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java6
-rw-r--r--packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java30
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java10
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java109
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java10
-rw-r--r--packages/SystemUI/Android.bp6
-rw-r--r--packages/SystemUI/docs/physics-animation-layout-config-methods.pngbin0 -> 106340 bytes
-rw-r--r--packages/SystemUI/docs/physics-animation-layout-control-methods.pngbin0 -> 70507 bytes
-rw-r--r--packages/SystemUI/docs/physics-animation-layout.md56
-rw-r--r--packages/SystemUI/docs/physics-animation-testing.md11
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java12
-rw-r--r--packages/SystemUI/res/layout/bubble_view.xml4
-rw-r--r--packages/SystemUI/res/layout/global_actions_grid.xml83
-rw-r--r--packages/SystemUI/res/layout/global_actions_grid_item.xml61
-rw-r--r--packages/SystemUI/res/layout/qs_footer_carrier.xml49
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml33
-rw-r--r--packages/SystemUI/res/values/dimens.xml26
-rw-r--r--packages/SystemUI/res/values/ids.xml9
-rw-r--r--packages/SystemUI/res/values/integers.xml6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierText.java406
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierTextController.java518
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/MultiListLayout.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java326
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java412
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java496
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java455
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java185
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java471
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java190
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java224
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java135
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java3
-rw-r--r--proto/src/metrics_constants/metrics_constants.proto54
-rw-r--r--proto/src/system_messages.proto2
-rw-r--r--proto/src/wifi.proto147
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java53
-rw-r--r--services/accessibility/java/com/android/server/accessibility/TouchExplorer.java4
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java195
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java131
-rw-r--r--services/core/java/com/android/server/NetworkManagementService.java362
-rw-r--r--services/core/java/com/android/server/ServiceWatcher.java152
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java12
-rw-r--r--services/core/java/com/android/server/VibratorService.java188
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java83
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java19
-rw-r--r--services/core/java/com/android/server/am/AppCompactor.java451
-rw-r--r--services/core/java/com/android/server/am/BaseErrorDialog.java6
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java21
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java1
-rw-r--r--services/core/java/com/android/server/am/CoreSettingsObserver.java2
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java15
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java10
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java6
-rw-r--r--services/core/java/com/android/server/attention/AttentionManagerService.java9
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java807
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java1024
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java2484
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java37
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java949
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java4
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java55
-rw-r--r--services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java23
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkAgentInfo.java29
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkNotificationManager.java33
-rw-r--r--services/core/java/com/android/server/connectivity/ProxyTracker.java18
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java2
-rw-r--r--services/core/java/com/android/server/content/ContentService.java16
-rw-r--r--services/core/java/com/android/server/display/ColorDisplayService.java614
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java20
-rw-r--r--services/core/java/com/android/server/display/DisplayTransformManager.java12
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java40
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java151
-rw-r--r--services/core/java/com/android/server/job/JobConcurrencyManager.java126
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java170
-rw-r--r--services/core/java/com/android/server/location/GeocoderProxy.java31
-rw-r--r--services/core/java/com/android/server/location/GnssVisibilityControl.java159
-rw-r--r--services/core/java/com/android/server/location/LocationProviderProxy.java57
-rw-r--r--services/core/java/com/android/server/location/OWNERS2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionServiceImpl.java76
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java35
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java2
-rw-r--r--services/core/java/com/android/server/notification/NotificationShellCmd.java8
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java202
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java28
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java28
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java27
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java4
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java13
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java51
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java1
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java8
-rw-r--r--services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java12
-rw-r--r--services/core/java/com/android/server/power/AttentionDetector.java235
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java20
-rw-r--r--services/core/java/com/android/server/role/RoleManagerService.java1
-rw-r--r--services/core/java/com/android/server/rollback/LocalIntentReceiver.java47
-rw-r--r--services/core/java/com/android/server/rollback/RollbackData.java8
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java192
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java98
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java75
-rw-r--r--services/core/java/com/android/server/signedconfig/SignatureVerifier.java4
-rw-r--r--services/core/java/com/android/server/stats/StatsCompanionService.java64
-rw-r--r--services/core/java/com/android/server/testharness/TestHarnessModeService.java5
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityDisplay.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java96
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java41
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java61
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java33
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java10
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java27
-rw-r--r--services/core/java/com/android/server/wm/AppWindowThumbnail.java6
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java40
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java10
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java36
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java10
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java27
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java6
-rw-r--r--services/core/java/com/android/server/wm/RootActivityContainer.java37
-rw-r--r--services/core/java/com/android/server/wm/Task.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskRecord.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java27
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfaceController.java6
-rw-r--r--services/core/jni/com_android_server_lights_LightsService.cpp44
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java97
-rw-r--r--services/java/com/android/server/SystemServer.java12
-rw-r--r--services/net/java/android/net/ip/IpClient.java320
-rw-r--r--services/net/java/android/net/shared/IpConfigurationParcelableUtil.java2
-rw-r--r--services/net/java/android/net/shared/NetworkObserverRegistry.java255
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java379
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java290
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java100
-rw-r--r--services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java181
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java340
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java71
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java102
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java86
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java5
-rw-r--r--services/usage/java/com/android/server/usage/AppTimeLimitController.java180
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java76
-rw-r--r--services/usb/java/com/android/server/usb/UsbPortManager.java64
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java6
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java107
-rw-r--r--startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java38
-rw-r--r--startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java194
-rw-r--r--startop/iorap/tests/AndroidTest.xml9
-rw-r--r--telephony/java/android/telephony/CallAttributes.java6
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java34
-rw-r--r--telephony/java/android/telephony/SmsManager.java75
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java9
-rw-r--r--telephony/java/android/telephony/euicc/EuiccManager.java26
-rw-r--r--telephony/java/android/telephony/ims/ImsException.java113
-rw-r--r--telephony/java/android/telephony/ims/ImsMmTelManager.java45
-rw-r--r--telephony/java/android/telephony/ims/ProvisioningManager.java160
-rw-r--r--telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java14
-rw-r--r--telephony/java/com/android/ims/ImsConfig.java6
-rw-r--r--telephony/java/com/android/ims/ImsException.java2
-rw-r--r--telephony/java/com/android/internal/telephony/ISms.aidl15
-rw-r--r--telephony/java/com/android/internal/telephony/ISmsImplBase.java6
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl18
-rw-r--r--telephony/java/com/android/internal/telephony/TelephonyPermissions.java58
-rw-r--r--test-mock/src/android/test/mock/MockCursor.java12
-rw-r--r--tests/DexLoggerIntegrationTests/Android.mk11
-rw-r--r--tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java11
-rw-r--r--tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java5
-rw-r--r--tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java2
-rw-r--r--tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java5
-rw-r--r--tests/JankBench/app/src/main/res/values/ids.xml1
-rw-r--r--tests/JankBench/app/src/main/res/values/strings.xml2
-rw-r--r--tests/JankBench/app/src/main/res/xml/benchmark.xml6
-rw-r--r--tests/RollbackTest/Android.mk13
-rw-r--r--tests/RollbackTest/TestApp/ACrashingV2.xml37
-rw-r--r--tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java (renamed from services/net/java/android/net/dhcp/DhcpClient.java)20
-rw-r--r--tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java4
-rw-r--r--tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java304
-rw-r--r--tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java15
-rw-r--r--tests/UsageStatsTest/AndroidManifest.xml1
-rw-r--r--tests/UsageStatsTest/res/menu/main.xml2
-rw-r--r--tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java56
-rw-r--r--tests/net/java/android/net/StaticIpConfigurationTest.java8
-rw-r--r--tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java2
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java92
-rw-r--r--tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java65
-rw-r--r--tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java16
-rw-r--r--tools/aapt2/Android.bp3
-rw-r--r--tools/aapt2/cmd/Convert.cpp14
-rw-r--r--tools/aapt2/cmd/Convert_test.cpp42
-rw-r--r--tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apkbin0 -> 15141221 bytes
-rw-r--r--tools/bit/aapt.cpp7
-rw-r--r--tools/bit/main.cpp36
-rw-r--r--tools/bit/make.cpp139
-rw-r--r--tools/bit/make.h26
-rw-r--r--tools/bit/print.cpp14
-rw-r--r--tools/bit/print.h1
-rw-r--r--tools/processors/view_inspector/Android.bp4
-rw-r--r--tools/signedconfig/debug_key.pem6
-rw-r--r--tools/signedconfig/debug_public.pem4
-rwxr-xr-xtools/signedconfig/debug_sign.sh2
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl2
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java25
-rw-r--r--wifi/java/android/net/wifi/WifiInfo.java29
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java45
-rw-r--r--wifi/java/android/net/wifi/aware/IdentityChangedListener.java2
-rw-r--r--wifi/java/android/net/wifi/aware/WifiAwareManager.java2
-rw-r--r--wifi/java/android/net/wifi/aware/WifiAwareSession.java4
-rw-r--r--wifi/java/android/net/wifi/aware/package.html2
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pManager.java16
-rw-r--r--wifi/java/com/android/server/wifi/BaseWifiService.java5
-rw-r--r--wifi/tests/src/android/net/wifi/WifiConfigurationTest.java4
-rw-r--r--wifi/tests/src/android/net/wifi/WifiInfoTest.java3
523 files changed, 20972 insertions, 8173 deletions
diff --git a/Android.bp b/Android.bp
index 813e98c38f92..e10cab65adb9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -271,6 +271,7 @@ java_defaults {
"core/java/android/os/IThermalService.aidl",
"core/java/android/os/IUpdateLock.aidl",
"core/java/android/os/IUserManager.aidl",
+ ":libvibrator_aidl",
"core/java/android/os/IVibratorService.aidl",
"core/java/android/os/storage/IStorageManager.aidl",
"core/java/android/os/storage/IStorageEventListener.aidl",
@@ -719,8 +720,6 @@ java_defaults {
exclude_srcs: [
// See comment on framework-atb-backward-compatibility module below
"core/java/android/content/pm/AndroidTestBaseUpdater.java",
- // See comment on framework-oahl-backward-compatibility module below
- "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
],
no_framework_libs: true,
@@ -728,7 +727,7 @@ java_defaults {
"ext",
],
- jarjar_rules: ":framework-hidl-jarjar",
+ jarjar_rules: "jarjar_rules_hidl.txt",
static_libs: [
"apex_aidl_interface-java",
@@ -740,6 +739,15 @@ java_defaults {
"android.hardware.cas-V1.0-java",
"android.hardware.contexthub-V1.0-java",
"android.hardware.health-V1.0-java-constants",
+ "android.hardware.radio-V1.0-java",
+ "android.hardware.radio-V1.1-java",
+ "android.hardware.radio-V1.2-java",
+ "android.hardware.radio-V1.3-java",
+ "android.hardware.radio-V1.4-java",
+ "android.hardware.radio.config-V1.0-java",
+ "android.hardware.radio.config-V1.1-java",
+ "android.hardware.radio.config-V1.2-java",
+ "android.hardware.radio.deprecated-V1.0-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
@@ -748,15 +756,12 @@ java_defaults {
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
"android.hardware.usb-V1.2-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
"android.hardware.vibrator-V1.0-java",
"android.hardware.vibrator-V1.1-java",
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.wifi-V1.0-java-constants",
- "android.hardware.radio-V1.0-java",
- "android.hardware.radio-V1.3-java",
- "android.hardware.radio-V1.4-java",
- "android.hardware.usb.gadget-V1.0-java",
"networkstack-aidl-interfaces-java",
"netd_aidl_interface-java",
"devicepolicyprotosnano",
@@ -791,8 +796,11 @@ filegroup {
}
filegroup {
- name: "framework-hidl-jarjar",
- srcs: ["jarjar_rules_hidl.txt"],
+ name: "libvibrator_aidl",
+ srcs: [
+ "core/java/android/os/IExternalVibrationController.aidl",
+ "core/java/android/os/IExternalVibratorService.aidl",
+ ],
}
java_library {
diff --git a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
index a7a81f2d20bb..767434d0831c 100644
--- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
+++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
@@ -91,7 +91,7 @@ public class TextClassifierPerfTest {
private static ConversationActions.Request createConversationActionsRequest(CharSequence text) {
ConversationActions.Message message =
new ConversationActions.Message.Builder(
- ConversationActions.Message.PERSON_USER_REMOTE)
+ ConversationActions.Message.PERSON_USER_OTHERS)
.setText(text)
.build();
return new ConversationActions.Request.Builder(Collections.singletonList(message))
diff --git a/api/current.txt b/api/current.txt
index 912d52e08f42..8ad59fff2721 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5101,7 +5101,7 @@ package android.app {
}
public class KeyguardManager {
- method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
+ method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
method @Deprecated public boolean inKeyguardRestrictedInputMode();
method public boolean isDeviceLocked();
@@ -5466,9 +5466,11 @@ package android.app {
public static final class Notification.BubbleMetadata implements android.os.Parcelable {
method public int describeContents();
+ method public boolean getAutoExpandBubble();
method public int getDesiredHeight();
method public android.graphics.drawable.Icon getIcon();
method public android.app.PendingIntent getIntent();
+ method public boolean getSuppressInitialNotification();
method public CharSequence getTitle();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR;
@@ -5477,9 +5479,11 @@ package android.app {
public static class Notification.BubbleMetadata.Builder {
ctor public Notification.BubbleMetadata.Builder();
method public android.app.Notification.BubbleMetadata build();
+ method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean);
method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int);
method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon);
method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
+ method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean);
method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence);
}
@@ -5793,6 +5797,7 @@ package android.app {
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method @Nullable public String getNotificationDelegate();
method public android.app.NotificationManager.Policy getNotificationPolicy();
+ method public boolean isNotificationAssistantAccessGranted(android.content.ComponentName);
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
@@ -6578,7 +6583,6 @@ package android.app.admin {
}
public class DevicePolicyManager {
- method public void addCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
@@ -6608,7 +6612,7 @@ package android.app.admin {
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
- method @NonNull public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+ method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
@@ -6698,7 +6702,6 @@ package android.app.admin {
method public int logoutUser(@NonNull android.content.ComponentName);
method public void reboot(@NonNull android.content.ComponentName);
method public void removeActiveAdmin(@NonNull android.content.ComponentName);
- method public boolean removeCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
method public boolean removeCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public boolean removeKeyPair(@Nullable android.content.ComponentName, @NonNull String);
method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int);
@@ -6720,6 +6723,7 @@ package android.app.admin {
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
+ method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
@@ -6787,7 +6791,7 @@ package android.app.admin {
method public void uninstallCaCert(@Nullable android.content.ComponentName, byte[]);
method public boolean updateOverrideApn(@NonNull android.content.ComponentName, int, @NonNull android.telephony.data.ApnSetting);
method public void wipeData(int);
- method public void wipeData(int, CharSequence);
+ method public void wipeData(int, @NonNull CharSequence);
field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
@@ -6931,6 +6935,7 @@ package android.app.admin {
field public static final int WIPE_EUICC = 4; // 0x4
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
+ field public static final int WIPE_SILENTLY = 8; // 0x8
}
public abstract static class DevicePolicyManager.InstallUpdateCallback {
@@ -10221,6 +10226,7 @@ package android.content {
field public static final String ACTION_MEDIA_REMOVED = "android.intent.action.MEDIA_REMOVED";
field public static final String ACTION_MEDIA_SCANNER_FINISHED = "android.intent.action.MEDIA_SCANNER_FINISHED";
field public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+ field public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME";
field public static final String ACTION_MEDIA_SCANNER_STARTED = "android.intent.action.MEDIA_SCANNER_STARTED";
field public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED";
field public static final String ACTION_MEDIA_UNMOUNTABLE = "android.intent.action.MEDIA_UNMOUNTABLE";
@@ -11218,6 +11224,7 @@ package android.content.pm {
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+ method @Nullable public android.content.pm.LauncherApps.AppUsageLimit getAppUsageLimit(String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method public java.util.List<android.os.UserHandle> getProfiles();
@@ -11245,6 +11252,14 @@ package android.content.pm {
field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
+ public static final class LauncherApps.AppUsageLimit implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getTotalUsageLimit();
+ method public long getUsageRemaining();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
+ }
+
public abstract static class LauncherApps.Callback {
ctor public LauncherApps.Callback();
method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -11420,6 +11435,7 @@ package android.content.pm {
method public int getSessionId();
method public long getSize();
method public int getStagedSessionErrorCode();
+ method public String getStagedSessionErrorMessage();
method public boolean isActive();
method public boolean isMultiPackage();
method public boolean isSealed();
@@ -11441,6 +11457,7 @@ package android.content.pm {
method public void setAppIcon(@Nullable android.graphics.Bitmap);
method public void setAppLabel(@Nullable CharSequence);
method public void setAppPackageName(@Nullable String);
+ method public void setInstallAsApex();
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setMultiPackage();
@@ -12495,6 +12512,7 @@ package android.database {
method public int getInt(int);
method public long getLong(int);
method public android.net.Uri getNotificationUri();
+ method public default java.util.List<android.net.Uri> getNotificationUris();
method public int getPosition();
method public short getShort(int);
method public String getString(int);
@@ -12518,6 +12536,7 @@ package android.database {
method public android.os.Bundle respond(android.os.Bundle);
method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+ method public default void setNotificationUris(@NonNull android.content.ContentResolver, @NonNull java.util.List<android.net.Uri>);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
field public static final int FIELD_TYPE_BLOB = 4; // 0x4
@@ -13930,8 +13949,6 @@ package android.graphics {
@AnyThread public abstract class ColorSpace {
method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[]);
method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[], @NonNull android.graphics.ColorSpace.Adaptation);
- method @NonNull @Size(3) public static float[] cctToIlluminantdXyz(@IntRange(from=1) int);
- method @NonNull @Size(9) public static float[] chromaticAdaptation(@NonNull android.graphics.ColorSpace.Adaptation, @NonNull @Size(min=2, max=3) float[], @NonNull @Size(min=2, max=3) float[]);
method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace);
method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace.RenderIntent);
method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace);
@@ -16481,6 +16498,7 @@ package android.hardware.biometrics {
ctor public BiometricPrompt.Builder(android.content.Context);
method public android.hardware.biometrics.BiometricPrompt build();
method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
+ method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean);
method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean);
method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
@@ -25936,7 +25954,6 @@ package android.media {
method @NonNull public abstract android.media.MediaSession2 onGetPrimarySession();
method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
method public final void removeSession(@NonNull android.media.MediaSession2);
- field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
}
public static class MediaSession2Service.MediaNotification {
@@ -27459,6 +27476,7 @@ package android.media.session {
method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
method public void notifySession2Created(@NonNull android.media.Session2Token);
+ method public void notifySession2Destroyed(@NonNull android.media.Session2Token);
method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
}
@@ -29219,6 +29237,7 @@ package android.net {
method public android.os.ParcelFileDescriptor establish();
method public android.net.VpnService.Builder setBlocking(boolean);
method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent);
+ method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo);
method public android.net.VpnService.Builder setMtu(int);
method public android.net.VpnService.Builder setSession(String);
method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]);
@@ -29827,7 +29846,7 @@ package android.net.wifi {
method @Deprecated public boolean disableNetwork(int);
method @Deprecated public boolean disconnect();
method @Deprecated public boolean enableNetwork(int, boolean);
- method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
+ method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
method public android.net.wifi.WifiInfo getConnectionInfo();
method public android.net.DhcpInfo getDhcpInfo();
method public int getMaxNumberOfNetworkSuggestionsPerApp();
@@ -30318,26 +30337,26 @@ package android.net.wifi.p2p {
}
public class WifiP2pManager {
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener);
method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener);
method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener);
method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener);
- method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener);
method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener);
method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener);
@@ -35285,11 +35304,16 @@ package android.os {
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createPrebaked(int);
method public static android.os.VibrationEffect createWaveform(long[], int);
method public static android.os.VibrationEffect createWaveform(long[], int[], int);
method public int describeContents();
field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ field public static final int EFFECT_CLICK = 0; // 0x0
+ field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
+ field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
+ field public static final int EFFECT_TICK = 2; // 0x2
}
public abstract class Vibrator {
@@ -38726,6 +38750,7 @@ package android.provider {
public static final class Settings.Panel {
field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY";
+ field public static final String ACTION_NFC = "android.settings.panel.action.NFC";
field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME";
}
@@ -41371,6 +41396,22 @@ package android.service.media {
package android.service.notification {
+ public final class Adjustment implements android.os.Parcelable {
+ ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
+ method public int describeContents();
+ method public CharSequence getExplanation();
+ method public String getKey();
+ method public String getPackage();
+ method public android.os.Bundle getSignals();
+ method public int getUser();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
+ field public static final String KEY_IMPORTANCE = "key_importance";
+ field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+ field public static final String KEY_SMART_REPLIES = "key_smart_replies";
+ field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+ }
+
public final class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, String, int);
ctor public Condition(android.net.Uri, String, String, String, int, int, int);
@@ -41417,6 +41458,24 @@ package android.service.notification {
field @Deprecated public static final String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
+ public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
+ ctor public NotificationAssistantService();
+ method public final void adjustNotification(android.service.notification.Adjustment);
+ method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
+ method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNotificationDirectReplied(@NonNull String);
+ method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
+ method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
+ method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
+ method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
+ method public void onNotificationsSeen(java.util.List<java.lang.String>);
+ method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
+ field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
+ field public static final int SOURCE_FROM_APP = 0; // 0x0
+ field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
+ }
+
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void cancelAllNotifications();
@@ -41497,6 +41556,8 @@ package android.service.notification {
method public long getLastAudiblyAlertedMillis();
method public String getOverrideGroupKey();
method public int getRank();
+ method public java.util.List<android.app.Notification.Action> getSmartActions();
+ method public java.util.List<java.lang.CharSequence> getSmartReplies();
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
@@ -41515,6 +41576,37 @@ package android.service.notification {
field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
}
+ public final class NotificationStats implements android.os.Parcelable {
+ ctor public NotificationStats();
+ method public int describeContents();
+ method public int getDismissalSentiment();
+ method public int getDismissalSurface();
+ method public boolean hasDirectReplied();
+ method public boolean hasExpanded();
+ method public boolean hasInteracted();
+ method public boolean hasSeen();
+ method public boolean hasSnoozed();
+ method public boolean hasViewedSettings();
+ method public void setDirectReplied();
+ method public void setDismissalSentiment(int);
+ method public void setDismissalSurface(int);
+ method public void setExpanded();
+ method public void setSeen();
+ method public void setSnoozed();
+ method public void setViewedSettings();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+ field public static final int DISMISSAL_AOD = 2; // 0x2
+ field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+ field public static final int DISMISSAL_OTHER = 0; // 0x0
+ field public static final int DISMISSAL_PEEK = 1; // 0x1
+ field public static final int DISMISSAL_SHADE = 3; // 0x3
+ field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+ field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+ field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+ field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
+ }
+
public class StatusBarNotification implements android.os.Parcelable {
ctor @Deprecated public StatusBarNotification(String, String, int, String, int, int, int, android.app.Notification, android.os.UserHandle, long);
ctor public StatusBarNotification(android.os.Parcel);
@@ -43950,7 +44042,9 @@ package android.telephony {
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+ field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
+ field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
@@ -44569,6 +44663,7 @@ package android.telephony {
public final class SmsManager {
method public String createAppSpecificSmsToken(android.app.PendingIntent);
+ method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
method public java.util.ArrayList<java.lang.String> divideMessage(String);
method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
method public android.os.Bundle getCarrierConfigValues();
@@ -48475,6 +48570,7 @@ package android.view {
method public String getName();
method @Deprecated public int getOrientation();
method @Deprecated public int getPixelFormat();
+ method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
method public long getPresentationDeadlineNanos();
method public void getRealMetrics(android.util.DisplayMetrics);
method public void getRealSize(android.graphics.Point);
@@ -53253,8 +53349,8 @@ package android.view.textclassifier {
method @Nullable public CharSequence getText();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
- field public static final android.app.Person PERSON_USER_LOCAL;
- field public static final android.app.Person PERSON_USER_REMOTE;
+ field public static final android.app.Person PERSON_USER_OTHERS;
+ field public static final android.app.Person PERSON_USER_SELF;
}
public static final class ConversationActions.Message.Builder {
@@ -53405,6 +53501,7 @@ package android.view.textclassifier {
public final class TextClassificationManager {
method @NonNull public android.view.textclassifier.TextClassifier createTextClassificationSession(@NonNull android.view.textclassifier.TextClassificationContext);
+ method @NonNull public android.view.textclassifier.TextClassifier getLocalTextClassifier();
method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier();
method public void setTextClassificationSessionFactory(@Nullable android.view.textclassifier.TextClassificationSessionFactory);
method public void setTextClassifier(@Nullable android.view.textclassifier.TextClassifier);
@@ -53482,7 +53579,7 @@ package android.view.textclassifier {
public final class TextClassifierEvent implements android.os.Parcelable {
method public int describeContents();
method @NonNull public int[] getActionIndices();
- method @Nullable public String getEntityType();
+ method @NonNull public String[] getEntityTypes();
method public int getEventCategory();
method @Nullable public android.view.textclassifier.TextClassificationContext getEventContext();
method public int getEventIndex();
@@ -53495,6 +53592,7 @@ package android.view.textclassifier {
method public int getRelativeWordEndIndex();
method public int getRelativeWordStartIndex();
method @Nullable public String getResultId();
+ method public float getScore();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CATEGORY_CONVERSATION_ACTIONS = 3; // 0x3
field public static final int CATEGORY_LANGUAGE_DETECTION = 4; // 0x4
@@ -53529,7 +53627,7 @@ package android.view.textclassifier {
ctor public TextClassifierEvent.Builder(int, int);
method @NonNull public android.view.textclassifier.TextClassifierEvent build();
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setActionIndices(@NonNull int...);
- method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityType(@Nullable String);
+ method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEntityTypes(@NonNull java.lang.String...);
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventContext(@Nullable android.view.textclassifier.TextClassificationContext);
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventIndex(int);
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setEventTime(long);
@@ -53540,6 +53638,7 @@ package android.view.textclassifier {
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordEndIndex(int);
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setRelativeWordStartIndex(int);
method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setResultId(@Nullable String);
+ method @NonNull public android.view.textclassifier.TextClassifierEvent.Builder setScore(float);
}
public final class TextLanguage implements android.os.Parcelable {
@@ -62290,20 +62389,20 @@ package java.nio {
method public abstract Object array();
method public abstract int arrayOffset();
method public final int capacity();
- method public final java.nio.Buffer clear();
- method public final java.nio.Buffer flip();
+ method public java.nio.Buffer clear();
+ method public java.nio.Buffer flip();
method public abstract boolean hasArray();
method public final boolean hasRemaining();
method public abstract boolean isDirect();
method public abstract boolean isReadOnly();
method public final int limit();
- method public final java.nio.Buffer limit(int);
- method public final java.nio.Buffer mark();
+ method public java.nio.Buffer limit(int);
+ method public java.nio.Buffer mark();
method public final int position();
- method public final java.nio.Buffer position(int);
+ method public java.nio.Buffer position(int);
method public final int remaining();
- method public final java.nio.Buffer reset();
- method public final java.nio.Buffer rewind();
+ method public java.nio.Buffer reset();
+ method public java.nio.Buffer rewind();
}
public class BufferOverflowException extends java.lang.RuntimeException {
diff --git a/api/system-current.txt b/api/system-current.txt
index 3466e2e8301d..9b034611731d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -508,11 +508,13 @@ package android.app {
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
+ method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+ field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
@@ -883,8 +885,8 @@ package android.app.contentsuggestions {
public final class ClassificationsRequest implements android.os.Parcelable {
method public int describeContents();
- method public android.os.Bundle getExtras();
- method public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public java.util.List<android.app.contentsuggestions.ContentSelection> getSelections();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ClassificationsRequest> CREATOR;
}
@@ -898,8 +900,8 @@ package android.app.contentsuggestions {
public final class ContentClassification implements android.os.Parcelable {
ctor public ContentClassification(@NonNull String, @NonNull android.os.Bundle);
method public int describeContents();
- method public android.os.Bundle getExtras();
- method public String getId();
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentClassification> CREATOR;
}
@@ -907,8 +909,8 @@ package android.app.contentsuggestions {
public final class ContentSelection implements android.os.Parcelable {
ctor public ContentSelection(@NonNull String, @NonNull android.os.Bundle);
method public int describeContents();
- method public android.os.Bundle getExtras();
- method public String getId();
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.ContentSelection> CREATOR;
}
@@ -930,8 +932,8 @@ package android.app.contentsuggestions {
public final class SelectionsRequest implements android.os.Parcelable {
method public int describeContents();
- method public android.os.Bundle getExtras();
- method public android.graphics.Point getInterestPoint();
+ method @Nullable public android.os.Bundle getExtras();
+ method @Nullable public android.graphics.Point getInterestPoint();
method public int getTaskId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.contentsuggestions.SelectionsRequest> CREATOR;
@@ -1053,6 +1055,7 @@ package android.app.role {
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+ field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
}
public interface RoleManagerCallback {
@@ -1113,6 +1116,7 @@ package android.app.usage {
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
method public int getUsageSource();
+ method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1120,6 +1124,7 @@ package android.app.usage {
method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+ method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(String, long, android.os.UserHandle);
@@ -1156,14 +1161,18 @@ package android.bluetooth {
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public String getMetadata(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean getSilenceMode();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, String);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean);
field public static final int ACCESS_ALLOWED = 1; // 0x1
field public static final int ACCESS_REJECTED = 2; // 0x2
field public static final int ACCESS_UNKNOWN = 0; // 0x0
+ field public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+ field public static final String EXTRA_SILENCE_ENABLED = "android.bluetooth.device.extra.SILENCE_ENABLED";
field public static final int METADATA_COMPANION_APP = 4; // 0x4
field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
field public static final int METADATA_HARDWARE_VERSION = 3; // 0x3
@@ -1296,13 +1305,13 @@ package android.content {
field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
- field public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE";
field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
+ field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
@@ -1688,18 +1697,17 @@ package android.content.rollback {
public final class RollbackInfo implements android.os.Parcelable {
method public int describeContents();
+ method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
method public int getRollbackId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
- field public final android.content.rollback.PackageRollbackInfo targetPackage;
}
public final class RollbackManager {
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks();
- method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData();
}
@@ -1805,9 +1813,16 @@ package android.hardware.display {
}
public final class ColorDisplayManager {
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getNightDisplayAutoMode();
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities();
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setAppSaturationLevel(@NonNull String, @IntRange(from=0, to=100) int);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayAutoMode(int);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomEndTime(@NonNull java.time.LocalTime);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setNightDisplayCustomStartTime(@NonNull java.time.LocalTime);
method @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean setSaturationLevel(@IntRange(from=0, to=100) int);
+ field public static final int AUTO_MODE_CUSTOM_TIME = 1; // 0x1
+ field public static final int AUTO_MODE_DISABLED = 0; // 0x0
+ field public static final int AUTO_MODE_TWILIGHT = 2; // 0x2
field public static final int CAPABILITY_HARDWARE_ACCELERATION_GLOBAL = 2; // 0x2
field public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 4; // 0x4
field public static final int CAPABILITY_NONE = 0; // 0x0
@@ -1839,9 +1854,15 @@ package android.hardware.hdmi {
public final class HdmiControlManager {
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
method @Nullable public android.hardware.hdmi.HdmiClient getClient(int);
+ method @Nullable public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevicesList();
+ method public int getPhysicalAddress();
method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient();
+ method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient();
+ method public boolean isRemoteDeviceConnected(android.hardware.hdmi.HdmiDeviceInfo);
+ method public void powerOffRemoteDevice(android.hardware.hdmi.HdmiDeviceInfo);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
+ method public void requestRemoteDeviceToBecomeActiveSource(android.hardware.hdmi.HdmiDeviceInfo);
method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
field public static final int AVR_VOLUME_MUTED = 101; // 0x65
@@ -1931,6 +1952,9 @@ package android.hardware.hdmi {
field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa
}
+ @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
+ }
+
public static interface HdmiControlManager.HotplugEventListener {
method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent);
}
@@ -2057,6 +2081,16 @@ package android.hardware.hdmi {
public abstract static class HdmiRecordSources.RecordSource {
}
+ public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
+ method public int getDeviceType();
+ method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+ method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+ }
+
+ public static interface HdmiSwitchClient.OnSelectListener {
+ method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int);
+ }
+
public class HdmiTimerRecordSources {
method public static boolean checkTimerRecordSource(int, byte[]);
method public static android.hardware.hdmi.HdmiTimerRecordSources.Duration durationOf(int, int);
@@ -3381,6 +3415,15 @@ package android.media {
method public void stop();
}
+ public final class Session2Token implements android.os.Parcelable {
+ ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle);
+ method public void destroy();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getPid();
+ method public boolean isDestroyed();
+ field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service";
+ }
+
public static class SubtitleData.Builder {
ctor public SubtitleData.Builder();
ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData);
@@ -4207,6 +4250,24 @@ package android.net {
field public final android.net.RssiCurve rssiCurve;
}
+ public final class StaticIpConfiguration implements android.os.Parcelable {
+ ctor public StaticIpConfiguration();
+ ctor public StaticIpConfiguration(android.net.StaticIpConfiguration);
+ method public void addDnsServer(java.net.InetAddress);
+ method public void clear();
+ method public int describeContents();
+ method public java.util.List<java.net.InetAddress> getDnsServers();
+ method public String getDomains();
+ method public java.net.InetAddress getGateway();
+ method public android.net.LinkAddress getIpAddress();
+ method public java.util.List<android.net.RouteInfo> getRoutes(String);
+ method public void setDomains(String);
+ method public void setGateway(java.net.InetAddress);
+ method public void setIpAddress(android.net.LinkAddress);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+ }
+
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -4232,6 +4293,47 @@ package android.net {
}
+package android.net.apf {
+
+ public class ApfCapabilities {
+ ctor public ApfCapabilities(int, int, int);
+ method public boolean hasDataAccess();
+ field public final int apfPacketFormat;
+ field public final int apfVersionSupported;
+ field public final int maximumApfProgramSize;
+ }
+
+}
+
+package android.net.captiveportal {
+
+ public final class CaptivePortalProbeResult {
+ ctor public CaptivePortalProbeResult(int);
+ ctor public CaptivePortalProbeResult(int, String, String);
+ ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec);
+ method public boolean isFailed();
+ method public boolean isPortal();
+ method public boolean isSuccessful();
+ field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
+ field public static final int FAILED_CODE = 599; // 0x257
+ field public static final int PORTAL_CODE = 302; // 0x12e
+ field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
+ field public static final int SUCCESS_CODE = 204; // 0xcc
+ field public final String detectUrl;
+ field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
+ field public final String redirectUrl;
+ }
+
+ public abstract class CaptivePortalProbeSpec {
+ method public String getEncodedSpec();
+ method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
+ method public java.net.URL getUrl();
+ method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String);
+ method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
+ }
+
+}
+
package android.net.metrics {
public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -4645,14 +4747,13 @@ package android.net.wifi {
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
- method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
method public boolean isDeviceToDeviceRttSupported();
method public boolean isPortableHotspotSupported();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
- method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler);
method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int);
@@ -4662,7 +4763,7 @@ package android.net.wifi {
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
- method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
+ method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
@@ -4698,19 +4799,6 @@ package android.net.wifi {
method public void onSuccess();
}
- public static interface WifiManager.NetworkRequestMatchCallback {
- method public void onAbort();
- method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>);
- method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback);
- method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration);
- method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration);
- }
-
- public static interface WifiManager.NetworkRequestUserSelectionCallback {
- method public void reject();
- method public void select(@NonNull android.net.wifi.WifiConfiguration);
- }
-
public static interface WifiManager.WifiUsabilityStatsListener {
method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry);
}
@@ -5014,6 +5102,7 @@ package android.nfc {
package android.os {
public class BatteryManager {
+ method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
}
@@ -5459,8 +5548,9 @@ package android.permission {
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract int onCountPermissionApps(@NonNull java.util.List<java.lang.String>, boolean, boolean);
method @NonNull public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
+ method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onGetPermissionUsages(boolean, long);
method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream);
- method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onPermissionUsageResult(boolean, long);
+ method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String);
method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String);
field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
@@ -5504,7 +5594,6 @@ package android.permissionpresenterservice {
method @Deprecated public final void attachBaseContext(android.content.Context);
method @Deprecated public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated public abstract java.util.List<android.content.pm.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
- method @Deprecated public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
field @Deprecated public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService";
}
@@ -5624,6 +5713,18 @@ package android.provider {
field public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
}
+ public static interface DeviceConfig.ActivityManager {
+ field public static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
+ field public static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
+ field public static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+ field public static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+ field public static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+ field public static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+ field public static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+ field public static final String KEY_USE_COMPACTION = "use_compaction";
+ field public static final String NAMESPACE = "activity_manager";
+ }
+
public static interface DeviceConfig.FsiBoot {
field public static final String NAMESPACE = "fsi_boot";
field public static final String OOB_ENABLED = "oob_enabled";
@@ -5640,6 +5741,11 @@ package android.provider {
method public void onPropertyChanged(String, String, String);
}
+ public static interface DeviceConfig.Storage {
+ field public static final String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+ field public static final String NAMESPACE = "storage";
+ }
+
public static interface DeviceConfig.Telephony {
field public static final String NAMESPACE = "telephony";
field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
@@ -6342,77 +6448,6 @@ package android.service.euicc {
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public CharSequence getExplanation();
- method public String getKey();
- method public String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final String KEY_IMPORTANCE = "key_importance";
- field public static final String KEY_PEOPLE = "key_people";
- field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
- field public static final String KEY_SMART_REPLIES = "key_smart_replies";
- field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
- }
-
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReplied(@NonNull String);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
- method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
- method public void onNotificationsSeen(java.util.List<java.lang.String>);
- method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
- method public final void unsnoozeNotification(String);
- field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- field public static final int SOURCE_FROM_APP = 0; // 0x0
- field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
- }
-
- public final class NotificationStats implements android.os.Parcelable {
- ctor public NotificationStats();
- ctor protected NotificationStats(android.os.Parcel);
- method public int describeContents();
- method public int getDismissalSentiment();
- method public int getDismissalSurface();
- method public boolean hasDirectReplied();
- method public boolean hasExpanded();
- method public boolean hasInteracted();
- method public boolean hasSeen();
- method public boolean hasSnoozed();
- method public boolean hasViewedSettings();
- method public void setDirectReplied();
- method public void setDismissalSentiment(int);
- method public void setDismissalSurface(int);
- method public void setExpanded();
- method public void setSeen();
- method public void setSnoozed();
- method public void setViewedSettings();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
- field public static final int DISMISSAL_AOD = 2; // 0x2
- field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
- field public static final int DISMISSAL_OTHER = 0; // 0x0
- field public static final int DISMISSAL_PEEK = 1; // 0x1
- field public static final int DISMISSAL_SHADE = 3; // 0x3
- field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
- field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
- field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
- field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
- }
-
public final class SnoozeCriterion implements android.os.Parcelable {
ctor public SnoozeCriterion(String, CharSequence, CharSequence);
ctor protected SnoozeCriterion(android.os.Parcel);
@@ -8036,10 +8071,10 @@ package android.telephony.euicc {
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
- field public static final String ACTION_PROFILE_SELECTION = "android.telephony.euicc.action.PROFILE_SELECTION";
field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
field public static final String ACTION_RENAME_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.RENAME_SUBSCRIPTION_PRIVILEGED";
field public static final String ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.TOGGLE_SUBSCRIPTION_PRIVILEGED";
+ field public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; // 0x4
field public static final int EUICC_ACTIVATION_TYPE_BACKUP = 2; // 0x2
field public static final int EUICC_ACTIVATION_TYPE_DEFAULT = 1; // 0x1
field public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3; // 0x3
@@ -8269,6 +8304,16 @@ package android.telephony.ims {
field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants;
}
+ public class ImsException extends java.lang.Exception {
+ ctor public ImsException(@Nullable String);
+ ctor public ImsException(@Nullable String, int);
+ ctor public ImsException(@Nullable String, int, Throwable);
+ method public int getCode();
+ field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
+ field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
+ field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
+ }
+
public final class ImsExternalCallState implements android.os.Parcelable {
ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean);
method public int describeContents();
@@ -8286,7 +8331,7 @@ package android.telephony.ims {
}
public class ImsMmTelManager {
- method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int);
+ method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled();
@@ -8295,8 +8340,8 @@ package android.telephony.ims {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int);
@@ -8731,13 +8776,21 @@ package android.telephony.ims {
}
public class ProvisioningManager {
- method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningIntValue(int, int);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setProvisioningStringValue(int, String);
+ method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
+ method @WorkerThread @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
+ field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+ field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
+ field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
+ field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
+ field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
}
public static class ProvisioningManager.Callback {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3dfd6e42e309..049e0025a59b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6,14 +6,20 @@ package android {
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+ field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+ field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
}
+ public static final class R.array {
+ field public static final int config_defaultRoleHolders = 17235974; // 0x1070006
+ }
+
}
package android.animation {
@@ -332,6 +338,7 @@ package android.app.role {
method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.role.RoleManagerCallback);
+ field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
}
public interface RoleManagerCallback {
@@ -860,6 +867,24 @@ package android.net {
field public static final int RTN_UNREACHABLE = 7; // 0x7
}
+ public final class StaticIpConfiguration implements android.os.Parcelable {
+ ctor public StaticIpConfiguration();
+ ctor public StaticIpConfiguration(android.net.StaticIpConfiguration);
+ method public void addDnsServer(java.net.InetAddress);
+ method public void clear();
+ method public int describeContents();
+ method public java.util.List<java.net.InetAddress> getDnsServers();
+ method public String getDomains();
+ method public java.net.InetAddress getGateway();
+ method public android.net.LinkAddress getIpAddress();
+ method public java.util.List<android.net.RouteInfo> getRoutes(String);
+ method public void setDomains(String);
+ method public void setGateway(java.net.InetAddress);
+ method public void setIpAddress(android.net.LinkAddress);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+ }
+
public class TrafficStats {
method public static long getLoopbackRxBytes();
method public static long getLoopbackRxPackets();
@@ -869,6 +894,47 @@ package android.net {
}
+package android.net.apf {
+
+ public class ApfCapabilities {
+ ctor public ApfCapabilities(int, int, int);
+ method public boolean hasDataAccess();
+ field public final int apfPacketFormat;
+ field public final int apfVersionSupported;
+ field public final int maximumApfProgramSize;
+ }
+
+}
+
+package android.net.captiveportal {
+
+ public final class CaptivePortalProbeResult {
+ ctor public CaptivePortalProbeResult(int);
+ ctor public CaptivePortalProbeResult(int, String, String);
+ ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec);
+ method public boolean isFailed();
+ method public boolean isPortal();
+ method public boolean isSuccessful();
+ field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
+ field public static final int FAILED_CODE = 599; // 0x257
+ field public static final int PORTAL_CODE = 302; // 0x12e
+ field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
+ field public static final int SUCCESS_CODE = 204; // 0xcc
+ field public final String detectUrl;
+ field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
+ field public final String redirectUrl;
+ }
+
+ public abstract class CaptivePortalProbeSpec {
+ method public String getEncodedSpec();
+ method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
+ method public java.net.URL getUrl();
+ method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String);
+ method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
+ }
+
+}
+
package android.net.metrics {
public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -1047,6 +1113,10 @@ package android.os {
method public static java.io.File getStorageDirectory();
}
+ public class FileUtils {
+ method public static boolean contains(java.io.File, java.io.File);
+ }
+
public abstract class HwBinder implements android.os.IHwBinder {
ctor public HwBinder();
method public static final void configureRpcThreadpool(long, boolean);
@@ -1212,6 +1282,10 @@ package android.os {
method public boolean hasSingleFileDescriptor();
}
+ public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+ method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException;
+ }
+
public final class PowerManager {
method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveMode();
method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSavings(boolean, int);
@@ -1299,15 +1373,11 @@ package android.os {
method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
method public abstract long getDuration();
method protected static int scale(int, float, int);
- field public static final int EFFECT_CLICK = 0; // 0x0
- field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
- field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
field public static final int EFFECT_POP = 4; // 0x4
field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
field public static final int EFFECT_STRENGTH_STRONG = 2; // 0x2
field public static final int EFFECT_THUD = 3; // 0x3
- field public static final int EFFECT_TICK = 2; // 0x2
field public static final int[] RINGTONES;
}
@@ -1483,17 +1553,37 @@ package android.print {
package android.provider {
+ public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns {
+ field public static final String[] SYNC_WRITABLE_COLUMNS;
+ }
+
+ public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns {
+ field public static final String[] SYNC_WRITABLE_COLUMNS;
+ }
+
+ public final class ContactsContract {
+ field public static final String HIDDEN_COLUMN_PREFIX = "x_";
+ }
+
public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
field public static final android.net.Uri ENTERPRISE_CONTENT_URI;
}
+ public static final class ContactsContract.PinnedPositions {
+ field public static final String UNDEMOTE_METHOD = "undemote";
+ }
+
public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns {
field public static final android.net.Uri CORP_CONTENT_URI;
}
public final class MediaStore {
- method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+ 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 public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+ field public static final String SCAN_FILE_CALL = "scan_file";
+ field public static final String SCAN_VOLUME_CALL = "scan_volume";
}
public final class Settings {
@@ -1553,6 +1643,10 @@ package android.provider {
field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
}
+ public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
+ field public static final String _DATA = "_data";
+ }
+
}
package android.security {
@@ -1751,84 +1845,14 @@ package android.service.autofill.augmented {
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public CharSequence getExplanation();
- method public String getKey();
- method public String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final String KEY_IMPORTANCE = "key_importance";
- field public static final String KEY_PEOPLE = "key_people";
- field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
- field public static final String KEY_SMART_REPLIES = "key_smart_replies";
- field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
- }
-
@Deprecated public abstract class ConditionProviderService extends android.app.Service {
method @Deprecated public boolean isBound();
}
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReplied(@NonNull String);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
- method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
- method public void onNotificationsSeen(java.util.List<java.lang.String>);
- method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
- method public final void unsnoozeNotification(String);
- field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- field public static final int SOURCE_FROM_APP = 0; // 0x0
- field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
- }
-
public abstract class NotificationListenerService extends android.app.Service {
method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
}
- public final class NotificationStats implements android.os.Parcelable {
- ctor public NotificationStats();
- ctor protected NotificationStats(android.os.Parcel);
- method public int describeContents();
- method public int getDismissalSentiment();
- method public int getDismissalSurface();
- method public boolean hasDirectReplied();
- method public boolean hasExpanded();
- method public boolean hasInteracted();
- method public boolean hasSeen();
- method public boolean hasSnoozed();
- method public boolean hasViewedSettings();
- method public void setDirectReplied();
- method public void setDismissalSentiment(int);
- method public void setDismissalSurface(int);
- method public void setExpanded();
- method public void setSeen();
- method public void setSnoozed();
- method public void setViewedSettings();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
- field public static final int DISMISSAL_AOD = 2; // 0x2
- field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
- field public static final int DISMISSAL_OTHER = 0; // 0x0
- field public static final int DISMISSAL_PEEK = 1; // 0x1
- field public static final int DISMISSAL_SHADE = 3; // 0x3
- field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
- field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
- field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
- field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
- }
-
public final class SnoozeCriterion implements android.os.Parcelable {
ctor public SnoozeCriterion(String, CharSequence, CharSequence);
ctor protected SnoozeCriterion(android.os.Parcel);
@@ -1897,10 +1921,15 @@ package android.telephony {
}
public class TelephonyManager {
+ method public int checkCarrierPrivilegesForPackage(String);
method public int getCarrierIdListVersion();
method public boolean isRttSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method public void setCarrierTestOverride(String, String, String, String, String, String, String);
+ field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
+ field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
+ field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
+ field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
}
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 3defdc5467c8..062ba655640e 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -102,7 +102,17 @@ public class Bmgr {
String op = nextArg();
Slog.v(TAG, "Running " + op + " for user:" + userId);
- if (!isBmgrActive(userId)) {
+ if (mBmgr == null) {
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ return;
+ }
+
+ if ("activate".equals(op)) {
+ doActivateService(userId);
+ return;
+ }
+
+ if (!isBackupActive(userId)) {
return;
}
@@ -175,12 +185,7 @@ public class Bmgr {
showUsage();
}
- boolean isBmgrActive(@UserIdInt int userId) {
- if (mBmgr == null) {
- System.err.println(BMGR_NOT_RUNNING_ERR);
- return false;
- }
-
+ boolean isBackupActive(@UserIdInt int userId) {
try {
if (!mBmgr.isBackupServiceActive(userId)) {
System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -845,6 +850,27 @@ public class Bmgr {
}
}
+ private void doActivateService(int userId) {
+ String arg = nextArg();
+ if (arg == null) {
+ showUsage();
+ return;
+ }
+
+ try {
+ boolean activate = Boolean.parseBoolean(arg);
+ mBmgr.setBackupServiceActive(userId, activate);
+ System.out.println(
+ "Backup service now "
+ + (activate ? "activated" : "deactivated")
+ + " for user "
+ + userId);
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ }
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -880,6 +906,7 @@ public class Bmgr {
System.err.println(" bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE...");
System.err.println(" bmgr cancel backups");
System.err.println(" bmgr init TRANSPORT...");
+ System.err.println(" bmgr activate BOOL");
System.err.println("");
System.err.println("The '--user' option specifies the user on which the operation is run.");
System.err.println("It must be the first argument before the operation.");
@@ -946,6 +973,11 @@ public class Bmgr {
System.err.println("");
System.err.println("The 'init' command initializes the given transports, wiping all data");
System.err.println("from their backing data stores.");
+ System.err.println("");
+ System.err.println("The 'activate' command activates or deactivates the backup service.");
+ System.err.println("If the argument is 'true' it will be activated, otherwise it will be");
+ System.err.println("deactivated. When deactivated, the service will not be running and no");
+ System.err.println("operations can be performed until activation.");
}
private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index c455ac0f83af..0c581f3b1a98 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -39,6 +39,7 @@ using android::idmap2::PolicyBitmask;
using android::idmap2::PolicyFlags;
using android::idmap2::Result;
using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
bool Create(const std::vector<std::string>& args, std::ostream& out_error) {
std::string target_apk_path;
@@ -66,6 +67,13 @@ bool Create(const std::vector<std::string>& args, std::ostream& out_error) {
return false;
}
+ const uid_t uid = getuid();
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ out_error << "error: uid " << uid << " does not have write access to " << idmap_path
+ << std::endl;
+ return false;
+ }
+
PolicyBitmask fulfilled_policies = 0;
if (auto result = PoliciesToBitmask(policies, out_error)) {
fulfilled_policies |= *result;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index a3c752718ee2..f30ce9b08d6e 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -27,6 +27,7 @@
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
+#include "binder/IPCThreadState.h"
#include "utils/String8.h"
#include "utils/Trace.h"
@@ -38,18 +39,19 @@
#include "idmap2d/Idmap2Service.h"
+using android::IPCThreadState;
using android::binder::Status;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::Idmap;
using android::idmap2::IdmapHeader;
using android::idmap2::PolicyBitmask;
using android::idmap2::Result;
+using android::idmap2::utils::kIdmapCacheDir;
using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
namespace {
-constexpr const char* kIdmapCacheDir = "/data/resource-cache";
-
Status ok() {
return Status::ok();
}
@@ -77,7 +79,13 @@ Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
assert(_aidl_return);
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ *_aidl_return = false;
+ return error(base::StringPrintf("failed to unlink %s: calling uid %d lacks write access",
+ idmap_path.c_str(), uid));
+ }
if (unlink(idmap_path.c_str()) != 0) {
*_aidl_return = false;
return error("failed to unlink " + idmap_path + ": " + strerror(errno));
@@ -118,6 +126,13 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path,
const PolicyBitmask policy_bitmask = ConvertAidlArgToPolicyBitmask(fulfilled_policies);
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+ return error(base::StringPrintf("will not write to %s: calling uid %d lacks write accesss",
+ idmap_path.c_str(), uid));
+ }
+
const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
if (!target_apk) {
return error("failed to load apk " + target_apk_path);
@@ -137,7 +152,6 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path,
}
umask(kIdmapFilePermissionMask);
- const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
std::ofstream fout(idmap_path);
if (fout.fail()) {
return error("failed to open idmap path " + idmap_path);
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index 5c41c49906cd..3f03236d5e1a 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,8 @@
#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+#include <sys/types.h>
+
#include <functional>
#include <memory>
#include <string>
@@ -24,6 +26,7 @@
namespace android::idmap2::utils {
+constexpr const char* kIdmapCacheDir = "/data/resource-cache";
constexpr const mode_t kIdmapFilePermissionMask = 0133; // u=rw,g=r,o=r
typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)>
@@ -35,6 +38,8 @@ std::unique_ptr<std::string> ReadFile(int fd);
std::unique_ptr<std::string> ReadFile(const std::string& path);
+bool UidHasWriteAccessToPath(uid_t uid, const std::string& path);
+
} // namespace android::idmap2::utils
#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
index 0255727fc8c6..a9b68cd6d5d5 100644
--- a/cmds/idmap2/libidmap2/FileUtils.cpp
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -19,12 +19,20 @@
#include <unistd.h>
#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <cstring>
#include <fstream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
+#include "android-base/file.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "private/android_filesystem_config.h"
+
#include "idmap2/FileUtils.h"
namespace android::idmap2::utils {
@@ -77,4 +85,26 @@ std::unique_ptr<std::string> ReadFile(int fd) {
return r == 0 ? std::move(str) : nullptr;
}
+#ifdef __ANDROID__
+bool UidHasWriteAccessToPath(uid_t uid, const std::string& path) {
+ // resolve symlinks and relative paths; the directories must exist
+ std::string canonical_path;
+ if (!base::Realpath(base::Dirname(path), &canonical_path)) {
+ return false;
+ }
+
+ const std::string cache_subdir = base::StringPrintf("%s/", kIdmapCacheDir);
+ if (canonical_path == kIdmapCacheDir ||
+ canonical_path.compare(0, cache_subdir.size(), cache_subdir) == 0) {
+ // limit access to /data/resource-cache to root and system
+ return uid == AID_ROOT || uid == AID_SYSTEM;
+ }
+ return true;
+}
+#else
+bool UidHasWriteAccessToPath(uid_t uid ATTRIBUTE_UNUSED, const std::string& path ATTRIBUTE_UNUSED) {
+ return true;
+}
+#endif
+
} // namespace android::idmap2::utils
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
index d9d9a7f829cf..45f84fe341cc 100644
--- a/cmds/idmap2/tests/FileUtilsTests.cpp
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -22,6 +22,8 @@
#include "gtest/gtest.h"
#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "private/android_filesystem_config.h"
#include "idmap2/FileUtils.h"
@@ -71,4 +73,25 @@ TEST(FileUtilsTests, ReadFile) {
close(pipefd[0]);
}
+#ifdef __ANDROID__
+TEST(FileUtilsTests, UidHasWriteAccessToPath) {
+ constexpr const char* tmp_path = "/data/local/tmp/test@idmap";
+ const std::string cache_path(base::StringPrintf("%s/test@idmap", kIdmapCacheDir));
+ const std::string sneaky_cache_path(base::StringPrintf("/data/../%s/test@idmap", kIdmapCacheDir));
+
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, tmp_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, cache_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_ROOT, sneaky_cache_path));
+
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, tmp_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, cache_path));
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SYSTEM, sneaky_cache_path));
+
+ constexpr const uid_t AID_SOME_APP = AID_SYSTEM + 1;
+ ASSERT_TRUE(UidHasWriteAccessToPath(AID_SOME_APP, tmp_path));
+ ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, cache_path));
+ ASSERT_FALSE(UidHasWriteAccessToPath(AID_SOME_APP, sneaky_cache_path));
+}
+#endif
+
} // namespace android::idmap2::utils
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
index 4334fa60767b..c550eafe5ffe 100644
--- a/cmds/idmap2/tests/Idmap2BinaryTests.cpp
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -38,6 +38,7 @@
#include "gtest/gtest.h"
#include "androidfw/PosixUtils.h"
+#include "private/android_filesystem_config.h"
#include "idmap2/FileUtils.h"
#include "idmap2/Idmap.h"
@@ -69,9 +70,23 @@ void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path,
ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \
} while (0)
+#ifdef __ANDROID__
+#define SKIP_TEST_IF_CANT_EXEC_IDMAP2 \
+ do { \
+ const uid_t uid = getuid(); \
+ if (uid != AID_ROOT && uid != AID_SYSTEM) { \
+ GTEST_SKIP(); \
+ } \
+ } while (0)
+#else
+#define SKIP_TEST_IF_CANT_EXEC_IDMAP2
+#endif
+
} // namespace
TEST_F(Idmap2BinaryTests, Create) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
// clang-format off
auto result = ExecuteBinary({"idmap2",
"create",
@@ -97,6 +112,8 @@ TEST_F(Idmap2BinaryTests, Create) {
}
TEST_F(Idmap2BinaryTests, Dump) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
// clang-format off
auto result = ExecuteBinary({"idmap2",
"create",
@@ -144,6 +161,8 @@ TEST_F(Idmap2BinaryTests, Dump) {
}
TEST_F(Idmap2BinaryTests, Scan) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk";
const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk";
const std::string idmap_static_1_path =
@@ -236,6 +255,8 @@ TEST_F(Idmap2BinaryTests, Scan) {
}
TEST_F(Idmap2BinaryTests, Lookup) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
// clang-format off
auto result = ExecuteBinary({"idmap2",
"create",
@@ -285,6 +306,8 @@ TEST_F(Idmap2BinaryTests, Lookup) {
}
TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) {
+ SKIP_TEST_IF_CANT_EXEC_IDMAP2;
+
const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST";
// missing mandatory options
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 9c320d3e2b03..b26c713877db 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -989,6 +989,25 @@ Status StatsService::setDataFetchOperation(int64_t key,
return Status::ok();
}
+Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
+ const String16& packageName,
+ vector<int64_t>* output) {
+ ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+
+ IPCThreadState* ipc = IPCThreadState::self();
+ mConfigManager->SetActiveConfigsChangedReceiver(ipc->getCallingUid(), intentSender);
+ //TODO: Return the list of configs that are already active
+ return Status::ok();
+}
+
+Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) {
+ ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+
+ IPCThreadState* ipc = IPCThreadState::self();
+ mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid());
+ return Status::ok();
+}
+
Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
ENFORCE_DUMP_AND_USAGE_STATS(packageName);
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index fe0504fc034f..cdff50fcb62e 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -132,6 +132,17 @@ public:
const String16& packageName) override;
/**
+ * Binder call to let clients register the active configs changed operation.
+ */
+ virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
+ const String16& packageName,
+ vector<int64_t>* output) override;
+
+ /**
+ * Binder call to remove the active configs changed operation for the specified package..
+ */
+ virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override;
+ /**
* Binder call to allow clients to remove the specified configuration.
*/
virtual Status removeConfiguration(int64_t key,
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 008a008cdee0..8e56bef6856e 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -28,12 +28,14 @@ import "frameworks/base/core/proto/android/bluetooth/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
import "frameworks/base/core/proto/android/debug/enums.proto";
+import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto";
import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
+import "frameworks/base/core/proto/android/service/usb.proto";
import "frameworks/base/core/proto/android/stats/enums.proto";
import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto";
import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
@@ -145,9 +147,9 @@ message Atom {
VibratorStateChanged vibrator_state_changed = 84;
DeferredJobStatsReported deferred_job_stats_reported = 85;
ThermalThrottlingStateChanged thermal_throttling = 86;
- FingerprintAcquired fingerprint_acquired = 87;
- FingerprintAuthenticated fingerprint_authenticated = 88;
- FingerprintErrorOccurred fingerprint_error_occurred = 89;
+ BiometricAcquired biometric_acquired = 87;
+ BiometricAuthenticated biometric_authenticated = 88;
+ BiometricErrorOccurred biometric_error_occurred = 89;
Notification notification = 90;
BatteryHealthSnapshot battery_health_snapshot = 91;
SlowIo slow_io = 92;
@@ -162,7 +164,7 @@ message Atom {
// Consider removing this if it becomes a problem
ServiceStateChanged service_state_changed = 99;
ServiceLaunchReported service_launch_reported = 100;
- PhenotypeFlagStateChanged phenotype_flag_state_changed = 101;
+ FlagFlipUpdateOccurred flag_flip_update_occurred = 101;
BinaryPushStateChanged binary_push_state_changed = 102;
DevicePolicyEvent device_policy_event = 103;
DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104;
@@ -207,10 +209,14 @@ message Atom {
AttentionManagerServiceResultReported attention_manager_service_result_reported = 143;
AdbConnectionChanged adb_connection_changed = 144;
SpeechDspStatReported speech_dsp_stat_reported = 145;
+ UsbContaminantReported usb_contaminant_reported = 146;
+ WatchdogRollbackOccurred watchdog_rollback_occurred = 147;
+ BiometricHalDeathReported biometric_hal_death_reported = 148;
+ BubbleUIChanged bubble_ui_changed = 149;
}
// Pulled events will start at field 10000.
- // Next: 10043
+ // Next: 10048
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -243,7 +249,7 @@ message Atom {
CategorySize category_size = 10028;
ProcStats proc_stats = 10029;
BatteryVoltage battery_voltage = 10030;
- NumFingerprints num_fingerprints = 10031;
+ NumBiometricsEnrolled num_fingerprints_enrolled = 10031;
DiskIo disk_io = 10032;
PowerProfile power_profile = 10033;
ProcStatsPkgProc proc_stats_pkg_proc = 10034;
@@ -258,6 +264,9 @@ message Atom {
BatteryLevel battery_level = 10043;
BuildInformation build_information = 10044;
BatteryCycleCount battery_cycle_count = 10045;
+ DebugElapsedClock debug_elapsed_clock = 10046;
+ DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
+ NumBiometricsEnrolled num_faces_enrolled = 10048;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -1481,6 +1490,25 @@ message BluetoothLinkLayerConnectionEvent {
optional android.bluetooth.hci.StatusEnum reason_code = 9;
}
+/**
+ * Logs when a module is rolled back by Watchdog.
+ *
+ * Logged from: Rollback Manager
+ */
+message WatchdogRollbackOccurred {
+ enum RollbackType {
+ UNKNOWN = 0;
+ ROLLBACK_INITIATE = 1;
+ ROLLBACK_SUCCESS = 2;
+ ROLLBACK_FAILURE = 3;
+ }
+ optional RollbackType rollback_type = 1;
+
+ optional string package_name = 2;
+
+ optional int32 package_version_code = 3;
+}
+
/**
* Logs when something is plugged into or removed from the USB-C connector.
@@ -2349,58 +2377,95 @@ message GenericAtom {
}
/**
- * Logs when a fingerprint acquire event occurs.
+ * Logs when a biometric acquire event occurs.
*
* Logged from:
- * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ * frameworks/base/services/core/java/com/android/server/biometrics
*/
-message FingerprintAcquired {
- // The associated user. Eg: 0 for owners, 10+ for others.
- // Defined in android/os/UserHandle.java
- optional int32 user = 1;
- // If this acquire is for a crypto fingerprint.
- // e.g. Secure purchases, unlock password storage.
- optional bool is_crypto = 2;
+message BiometricAcquired {
+ // Biometric modality that was acquired.
+ optional android.hardware.biometrics.ModalityEnum modality = 1;
+ // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java.
+ optional int32 user = 2;
+ // If this acquire is for a crypto operation. e.g. Secure purchases, unlock password storage.
+ optional bool is_crypto = 3;
+ // Action that the device is performing. Acquired messages are only expected for enroll and
+ // authenticate. Other actions may indicate an error.
+ optional android.hardware.biometrics.ActionEnum action = 4;
+ // The client that this acquisition was received for.
+ optional android.hardware.biometrics.ClientEnum client = 5;
+ // Acquired constants, e.g. ACQUIRED_GOOD. See constants defined by <Biometric>Manager.
+ optional int32 acquire_info = 6;
+ // Vendor-specific acquire info. Valid only if acquire_info == ACQUIRED_VENDOR.
+ optional int32 acquire_info_vendor = 7;
}
/**
- * Logs when a fingerprint authentication event occurs.
+ * Logs when a biometric authentication event occurs.
*
* Logged from:
- * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
- */
-message FingerprintAuthenticated {
- // The associated user. Eg: 0 for owners, 10+ for others.
- // Defined in android/os/UserHandle.java
- optional int32 user = 1;
- // If this authentication is for a crypto fingerprint.
- // e.g. Secure purchases, unlock password storage.
- optional bool is_crypto = 2;
- // Whether or not this authentication was successful.
- optional bool is_authenticated = 3;
+ * frameworks/base/services/core/java/com/android/server/biometrics
+ */
+message BiometricAuthenticated {
+ // Biometric modality that was used.
+ optional android.hardware.biometrics.ModalityEnum modality = 1;
+ // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java
+ optional int32 user = 2;
+ // If this authentication is for a crypto operation. e.g. Secure purchases, unlock password
+ // storage.
+ optional bool is_crypto = 3;
+ // The client that this acquisition was received for.
+ optional android.hardware.biometrics.ClientEnum client = 4;
+
+ enum State {
+ UNKNOWN = 0;
+ REJECTED = 1;
+ PENDING_CONFIRMATION = 2;
+ CONFIRMED = 3;
+ }
+
+ // State of the current auth attempt.
+ optional State state = 5;
+ // Time it took to authenticate. For BiometricPrompt where setRequireConfirmation(false) is
+ // specified and supported by the biometric modality, this is from the first ACQUIRED_GOOD to
+ // AUTHENTICATED. for setRequireConfirmation(true), this is from PENDING_CONFIRMATION to
+ // CONFIRMED.
+ optional int64 latency_millis = 6;
}
/**
- * Logs when a fingerprint error occurs.
+ * Logs when a biometric error occurs.
*
* Logged from:
- * frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ * frameworks/base/services/core/java/com/android/server/biometrics
+ */
+message BiometricErrorOccurred {
+ // Biometric modality that was used.
+ optional android.hardware.biometrics.ModalityEnum modality = 1;
+ // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java
+ optional int32 user = 2;
+ // If this error is for a crypto operation. e.g. Secure purchases, unlock password storage.
+ optional bool is_crypto = 3;
+ // Action that the device is performing.
+ optional android.hardware.biometrics.ActionEnum action = 4;
+ // The client that this acquisition was received for.
+ optional android.hardware.biometrics.ClientEnum client = 5;
+ // Error constants. See constants defined by <Biometric>Manager. Enums won't work since errors
+ // are unique to modality.
+ optional int32 error_info = 6;
+ // Vendor-specific error info. Valid only if acquire_info == ACQUIRED_VENDOR. These are defined
+ // by the vendor and not specified by the HIDL interface.
+ optional int32 error_info_vendor = 7;
+}
+
+/**
+ * Logs when a biometric HAL has crashed.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/biometrics
*/
-message FingerprintErrorOccurred {
- // The associated user. Eg: 0 for owners, 10+ for others.
- // Defined in android/os/UserHandle.java
- optional int32 user = 1;
- // If this error is for a crypto fingerprint.
- // e.g. Secure purchases, unlock password storage.
- optional bool is_crypto = 2;
-
- enum Error {
- UNKNOWN = 0;
- LOCKOUT = 1;
- PERMANENT_LOCKOUT = 2;
- }
- // The type of error.
- optional Error error = 3;
+message BiometricHalDeathReported {
+ // Biometric modality.
+ optional android.hardware.biometrics.ModalityEnum modality = 1;
}
message Notification {
@@ -2474,40 +2539,44 @@ message Notification {
}
/*
- * Logs when a flag flip state changes.
- * Logged in P/h.
+ * Logs when a flag flip update occurrs. Used for mainline modules that update via flag flips.
*/
-message PhenotypeFlagStateChanged {
- // Mendel configuration name.
- optional string mendel_config_name = 1;
- // State
- enum State {
- STATE_UNKNOWN = 0;
- COMMITTED = 1;
- }
- optional State state = 2;
+message FlagFlipUpdateOccurred {
+ // If the event is from a flag config package, specify the package name.
+ optional string flag_flip_package_name = 1;
+
+ // The order id of the package
+ optional int64 order_id = 2;
}
/*
* Logs when a binary push state changes.
- * Logged in Play store
+ * Logged by the installer via public api.
*/
message BinaryPushStateChanged {
- // Binary package name.
- optional string binary_name = 1;
- // Version code.
- optional int64 version = 2;
- // State
+ // Name of the train.
+ optional string train_name = 1;
+ // Version code for a "train" of packages that need to be installed atomically
+ optional int64 train_version_code = 2;
+ // After installation of this package, device requires a restart.
+ optional bool requires_staging = 3;
+ // Rollback should be enabled for this install.
+ optional bool rollback_enabled = 4;
+ // Requires low latency monitoring if possible.
+ optional bool requires_low_latency_monitor = 5;
+
enum State {
- STATE_UNKNOWN = 0;
- DOWNLOAD_START = 1;
- DOWNLOAD_DONE = 2;
- INSTALL_START = 3;
- INSTALL_DONE = 4;
- REBOOT_START = 5;
- REBOOT_DONE = 6;
+ UNKNOWN = 0;
+ INSTALL_REQUESTED = 1;
+ INSTALL_STARTED = 2;
+ INSTALL_STAGED_NOT_READY = 3;
+ INSTALL_STAGED_READY = 4;
+ INSTALL_SUCCESS = 5;
+ INSTALL_FAILURE = 6;
+ INSTALL_CANCELLED = 7;
+ INSTALLER_ROLLBACK_REQUESTED = 8;
}
- optional State state = 3;
+ optional State state = 6;
}
/** Represents USB port overheat event. */
@@ -3339,14 +3408,14 @@ message DiskIo {
/**
* Pulls the number of fingerprints for each user.
*
- * Pulled from StatsCompanionService, which queries FingerprintManager.
+ * Pulled from StatsCompanionService, which queries <Biometric>Manager.
*/
-message NumFingerprints {
+message NumBiometricsEnrolled {
// The associated user. Eg: 0 for owners, 10+ for others.
// Defined in android/os/UserHandle.java
optional int32 user = 1;
// Number of fingerprints registered to that user.
- optional int32 num_fingerprints = 2;
+ optional int32 num_enrolled = 2;
}
message AggStats {
@@ -4537,3 +4606,96 @@ message SpeechDspStatReported {
optional int32 total_crash_count = 3;
optional int32 total_recover_count = 4;
}
+
+/**
+ * Logs USB connector contaminant status.
+ *
+ * Logged from: USB Service.
+ */
+message UsbContaminantReported {
+ optional string id = 1;
+ optional android.service.usb.ContaminantPresenceStatus status = 2;
+}
+
+/**
+ * This atom is for debugging purpose.
+ */
+message DebugElapsedClock {
+ // Monotically increasing value for each pull.
+ optional int64 pull_count = 1;
+ // Time from System.elapsedRealtime.
+ optional int64 elapsed_clock_millis = 2;
+ // Time from System.elapsedRealtime.
+ optional int64 same_elapsed_clock_millis = 3;
+ // Diff between current elapsed time and elapsed time from previous pull.
+ optional int64 elapsed_clock_diff_millis = 4;
+
+ enum Type {
+ TYPE_UNKNOWN = 0;
+ ALWAYS_PRESENT = 1;
+ PRESENT_ON_ODD_PULLS = 2;
+ }
+ // Type of behavior for the pulled data.
+ optional Type type = 5;
+}
+
+/**
+ * This atom is for debugging purpose.
+ */
+message DebugFailingElapsedClock {
+ // Monotically increasing value for each pull.
+ optional int64 pull_count = 1;
+ // Time from System.elapsedRealtime.
+ optional int64 elapsed_clock_millis = 2;
+ // Time from System.elapsedRealtime.
+ optional int64 same_elapsed_clock_millis = 3;
+ // Diff between current elapsed time and elapsed time from previous pull.
+ optional int64 elapsed_clock_diff_millis = 4;
+}
+
+/** Logs System UI bubbles event changed.
+ *
+ * Logged from:
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/bubbles
+ */
+message BubbleUIChanged {
+
+ // The app package that is posting the bubble.
+ optional string package_name = 1;
+
+ // The notification channel that is posting the bubble.
+ optional string notification_channel = 2;
+
+ // The notification id associated with the posted bubble.
+ optional int32 notification_id = 3;
+
+ // The position of the bubble within the bubble stack.
+ optional int32 position = 4;
+
+ // The total number of bubbles within the bubble stack.
+ optional int32 total_number = 5;
+
+ // User interactions with the bubble.
+ enum Action {
+ UNKNOWN = 0;
+ POSTED = 1;
+ UPDATED = 2;
+ EXPANDED = 3;
+ COLLAPSED = 4;
+ DISMISSED = 5;
+ STACK_DISMISSED = 6;
+ STACK_MOVED = 7;
+ HEADER_GO_TO_APP = 8;
+ HEADER_GO_TO_SETTINGS = 9;
+ PERMISSION_OPT_IN = 10;
+ PERMISSION_OPT_OUT = 11;
+ PERMISSION_IGNORED = 12;
+ SWIPE_LEFT = 13;
+ SWIPE_RIGHT = 14;
+ }
+ optional Action action = 6;
+
+ // Normalized screen position of the bubble stack. The range is between 0 and 1.
+ optional float normalized_x_position = 7;
+ optional float normalized_y_position = 8;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 5fea90b61c80..aa22333ab26c 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -128,6 +128,17 @@ void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
mConfigReceivers.erase(key);
}
+void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
+ const sp<IBinder>& intentSender) {
+ lock_guard<mutex> lock(mMutex);
+ mActiveConfigsChangedReceivers[uid] = intentSender;
+}
+
+void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
+ lock_guard<mutex> lock(mMutex);
+ mActiveConfigsChangedReceivers.erase(uid);
+}
+
void ConfigManager::RemoveConfig(const ConfigKey& key) {
vector<sp<ConfigListener>> broadcastList;
{
@@ -181,6 +192,11 @@ void ConfigManager::RemoveConfigs(int uid) {
mConfigReceivers.erase(*it);
}
+ auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
+ if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
+ mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
+ }
+
mConfigs.erase(uidIt);
for (const sp<ConfigListener>& listener : mListeners) {
@@ -213,6 +229,7 @@ void ConfigManager::RemoveAllConfigs() {
}
mConfigReceivers.clear();
+ mActiveConfigsChangedReceivers.clear();
for (const sp<ConfigListener>& listener : mListeners) {
broadcastList.push_back(listener);
}
@@ -250,6 +267,17 @@ const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key
}
}
+const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+ lock_guard<mutex> lock(mMutex);
+
+ auto it = mActiveConfigsChangedReceivers.find(uid);
+ if (it == mActiveConfigsChangedReceivers.end()) {
+ return nullptr;
+ } else {
+ return it->second;
+ }
+}
+
void ConfigManager::Dump(FILE* out) {
lock_guard<mutex> lock(mMutex);
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 122e669057b0..c064a519f597 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -82,6 +82,23 @@ public:
void RemoveConfigReceiver(const ConfigKey& key);
/**
+ * Sets the broadcast receiver that is notified whenever the list of active configs
+ * changes for this uid.
+ */
+ void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender);
+
+ /**
+ * Returns the broadcast receiver for active configs changed for this uid.
+ */
+
+ const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const;
+
+ /**
+ * Erase any active configs changed broadcast receiver associated with this uid.
+ */
+ void RemoveActiveConfigsChangedReceiver(const int uid);
+
+ /**
* A configuration was removed.
*
* Reports this to listeners.
@@ -130,6 +147,12 @@ private:
std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
/**
+ * Each uid can be subscribed by up to one receiver to notify that the list of active configs
+ * for this uid has changed. The receiver is specified as IBinder from PendingIntent.
+ */
+ std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers;
+
+ /**
* The ConfigListeners that will be told about changes.
*/
std::vector<sp<ConfigListener>> mListeners;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 7e56beeefbf6..ba7bcc437d74 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -175,8 +175,8 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
{android::util::CATEGORY_SIZE,
{.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
// Number of fingerprints registered to each user.
- {android::util::NUM_FINGERPRINTS,
- {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}},
+ {android::util::NUM_FINGERPRINTS_ENROLLED,
+ {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
// ProcStats.
{android::util::PROC_STATS,
{.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
@@ -209,6 +209,14 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
{android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
{.puller = new StatsCompanionServicePuller(
android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+ // DebugElapsedClock.
+ {android::util::DEBUG_ELAPSED_CLOCK,
+ {.additiveFields = {1, 2, 3, 4},
+ .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+ // DebugFailingElapsedClock.
+ {android::util::DEBUG_FAILING_ELAPSED_CLOCK,
+ {.additiveFields = {1, 2, 3, 4},
+ .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
// BuildInformation.
{android::util::BUILD_INFORMATION,
{.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index ee2fe8f5750f..8e7a58b40d0c 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -901,7 +901,6 @@ Landroid/os/ParcelableParcel;->getClassLoader()Ljava/lang/ClassLoader;
Landroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel;
Landroid/os/ParcelFileDescriptor;-><init>(Ljava/io/FileDescriptor;)V
Landroid/os/ParcelFileDescriptor;->fromData([BLjava/lang/String;)Landroid/os/ParcelFileDescriptor;
-Landroid/os/ParcelFileDescriptor;->getFile(Ljava/io/FileDescriptor;)Ljava/io/File;
Landroid/os/ParcelFileDescriptor;->seekTo(J)J
Landroid/os/PerformanceCollector;-><init>()V
Landroid/os/PerformanceCollector;->beginSnapshot(Ljava/lang/String;)V
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3243a5de72a..ee3d27c7dac8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,9 +77,7 @@ import android.graphics.ImageDecoder;
import android.hardware.display.DisplayManagerGlobal;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
-import android.net.Network;
import android.net.Proxy;
-import android.net.ProxyInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -1033,15 +1031,10 @@ public final class ActivityThread extends ClientTransactionHandler {
NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
}
- public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
+ public void updateHttpProxy() {
final ConnectivityManager cm = ConnectivityManager.from(
getApplication() != null ? getApplication() : getSystemContext());
- final Network network = cm.getBoundNetworkForProcess();
- if (network != null) {
- Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
- } else {
- Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
- }
+ Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
}
public void processInBackground() {
@@ -5960,8 +5953,7 @@ public final class ActivityThread extends ClientTransactionHandler {
// crash if we can't get it.
final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
- final ProxyInfo proxyInfo = service.getProxyForNetwork(null);
- Proxy.setHttpProxySystemProperty(proxyInfo);
+ Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
} catch (RemoteException e) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 4d3711ae7ff5..47398674d74c 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -23,6 +23,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI
import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager.StackInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -119,6 +120,7 @@ public class ActivityView extends ViewGroup {
/** Callback that notifies when the container is ready or destroyed. */
public abstract static class StateCallback {
+
/**
* Called when the container is ready for launching activities. Calling
* {@link #startActivity(Intent)} prior to this callback will result in an
@@ -127,6 +129,7 @@ public class ActivityView extends ViewGroup {
* @see #startActivity(Intent)
*/
public abstract void onActivityViewReady(ActivityView view);
+
/**
* Called when the container can no longer launch activities. Calling
* {@link #startActivity(Intent)} after this callback will result in an
@@ -135,11 +138,24 @@ public class ActivityView extends ViewGroup {
* @see #startActivity(Intent)
*/
public abstract void onActivityViewDestroyed(ActivityView view);
+
+ /**
+ * Called when a task is created inside the container.
+ * This is a filtered version of {@link TaskStackListener}
+ */
+ public void onTaskCreated(int taskId, ComponentName componentName) { }
+
/**
* Called when a task is moved to the front of the stack inside the container.
* This is a filtered version of {@link TaskStackListener}
*/
public void onTaskMovedToFront(ActivityManager.StackInfo stackInfo) { }
+
+ /**
+ * Called when a task is about to be removed from the stack inside the container.
+ * This is a filtered version of {@link TaskStackListener}
+ */
+ public void onTaskRemovalStarted(int taskId) { }
}
/**
@@ -508,14 +524,45 @@ public class ActivityView extends ViewGroup {
@Override
public void onTaskMovedToFront(int taskId) throws RemoteException {
- if (mActivityViewCallback != null) {
- StackInfo stackInfo = getTopMostStackInfo();
- // if StackInfo was null or unrelated to the "move to front" then there's no use
- // notifying the callback
- if (stackInfo != null
- && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
- mActivityViewCallback.onTaskMovedToFront(stackInfo);
- }
+ if (mActivityViewCallback == null) {
+ return;
+ }
+
+ StackInfo stackInfo = getTopMostStackInfo();
+ // if StackInfo was null or unrelated to the "move to front" then there's no use
+ // notifying the callback
+ if (stackInfo != null
+ && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+ mActivityViewCallback.onTaskMovedToFront(stackInfo);
+ }
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
+ if (mActivityViewCallback == null) {
+ return;
+ }
+
+ StackInfo stackInfo = getTopMostStackInfo();
+ // if StackInfo was null or unrelated to the task creation then there's no use
+ // notifying the callback
+ if (stackInfo != null
+ && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+ mActivityViewCallback.onTaskCreated(taskId, componentName);
+ }
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) throws RemoteException {
+ if (mActivityViewCallback == null) {
+ return;
+ }
+ StackInfo stackInfo = getTopMostStackInfo();
+ // if StackInfo was null or task is on a different display then there's no use
+ // notifying the callback
+ if (stackInfo != null
+ && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+ mActivityViewCallback.onTaskRemovalStarted(taskId);
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ab2430c7a490..364d3c9c8e5d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -39,6 +39,7 @@ import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -1712,6 +1713,12 @@ public class AppOpsManager {
/** @hide */
public static final String KEY_HISTORICAL_OPS = "historical_ops";
+ /** System properties for debug logging of noteOp call sites */
+ private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled";
+ private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages";
+ private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops";
+ private static final String DEBUG_LOGGING_TAG = "AppOpsManager";
+
/**
* Retrieve the op switch that controls the given operation.
* @hide
@@ -4469,6 +4476,7 @@ public class AppOpsManager {
*/
@UnsupportedAppUsage
public int noteOpNoThrow(int op, int uid, String packageName) {
+ logNoteOpIfNeeded(op, packageName);
try {
return mService.noteOperation(op, uid, packageName);
} catch (RemoteException e) {
@@ -4834,4 +4842,45 @@ public class AppOpsManager {
return AppOpsManager.MODE_DEFAULT;
}
+
+ private static void logNoteOpIfNeeded(int op, String callingPackage) {
+ // Check if debug logging propety is enabled.
+ if (!SystemProperties.getBoolean(DEBUG_LOGGING_ENABLE_PROP, false)) {
+ return;
+ }
+ // Check if this package should be logged.
+ String packages = SystemProperties.get(DEBUG_LOGGING_PACKAGES_PROP, "");
+ if (!"".equals(packages) && callingPackage != null) {
+ boolean found = false;
+ for (String pkg : packages.split(",")) {
+ if (callingPackage.equals(pkg)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+ }
+ String opStr = opToName(op);
+ // Check if this app op should be logged
+ String logOps = SystemProperties.get(DEBUG_LOGGING_OPS_PROP, "");
+ if (!"".equals(logOps)) {
+ boolean found = false;
+ for (String logOp : logOps.split(",")) {
+ if (opStr.equals(logOp)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+ }
+
+ // Log a stack trace
+ Exception here = new Exception("HERE!");
+ android.util.Log.i(DEBUG_LOGGING_TAG, "Note operation package= " + callingPackage
+ + " op= " + opStr, here);
+ }
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index cc6c999ed255..db6ad3df17b0 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -434,6 +434,11 @@ interface IActivityTaskManager {
void registerRemoteAnimationForNextActivityStart(in String packageName,
in RemoteAnimationAdapter adapter);
+ /**
+ * Registers remote animations for a display.
+ */
+ void registerRemoteAnimationsForDisplay(int displayId, in RemoteAnimationDefinition definition);
+
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
void alwaysShowUnsupportedCompileSdkWarning(in ComponentName activity);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index fcb6c14d052c..c64fcf3e7526 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -100,8 +100,7 @@ oneway interface IApplicationThread {
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
void clearDnsCache();
- void setHttpProxy(in String proxy, in String port, in String exclList,
- in Uri pacFileUrl);
+ void updateHttpProxy();
void setCoreSettings(in Bundle coreSettings);
void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info);
void scheduleTrimMemory(int level);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 75c90542ebce..f522d71afd08 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.hardware.biometrics.BiometricPrompt;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -87,6 +88,12 @@ public class KeyguardManager {
"android.app.action.CONFIRM_FRP_CREDENTIAL";
/**
+ * @hide
+ */
+ public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE =
+ "android.app.extra.BIOMETRIC_PROMPT_BUNDLE";
+
+ /**
* A CharSequence dialog title to show to the user when used with a
* {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
* @hide
@@ -118,15 +125,19 @@ public class KeyguardManager {
public static final int RESULT_ALTERNATE = 1;
/**
- * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
- * for the current user of the device. The caller is expected to launch this activity using
- * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * @deprecated see {@link BiometricPrompt.Builder#setEnableFallback(boolean)}
+ *
+ * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
+ * if enrolled) for the current user of the device. The caller is expected to launch this
+ * activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
* {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
*
* @return the intent for launching the activity or null if no password is required.
**/
+ @Deprecated
@RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
- public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
+ public Intent createConfirmDeviceCredentialIntent(CharSequence title,
+ CharSequence description) {
if (!isDeviceSecure()) return null;
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b8d748dce9e6..7c550d4332fe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -84,7 +84,6 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -5257,7 +5256,11 @@ public class Notification implements Parcelable
* @hide
*/
public RemoteViews makeAmbientNotification() {
- return createHeadsUpContentView(false /* increasedHeight */);
+ RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
+ if (headsUpContentView != null) {
+ return headsUpContentView;
+ }
+ return createContentView();
}
private void hideLine1Text(RemoteViews result) {
@@ -8399,6 +8402,30 @@ public class Notification implements Parcelable
private CharSequence mTitle;
private Icon mIcon;
private int mDesiredHeight;
+ private int mFlags;
+
+ /**
+ * If set and the app creating the bubble is in the foreground, the bubble will be posted
+ * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
+ *
+ * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+ *
+ * <p>Generally this flag should only be set if the user has performed an action to request
+ * or create a bubble.</p>
+ */
+ private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+
+ /**
+ * If set and the app creating the bubble is in the foreground, the bubble will be posted
+ * <b>without</b> the associated notification in the notification shade. Subsequent update
+ * notifications to this bubble will post a notification in the shade.
+ *
+ * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+ *
+ * <p>Generally this flag should only be set if the user has performed an action to request
+ * or create a bubble.</p>
+ */
+ private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002;
private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) {
mPendingIntent = intent;
@@ -8412,6 +8439,7 @@ public class Notification implements Parcelable
mTitle = in.readCharSequence();
mIcon = Icon.CREATOR.createFromParcel(in);
mDesiredHeight = in.readInt();
+ mFlags = in.readInt();
}
/**
@@ -8444,6 +8472,24 @@ public class Notification implements Parcelable
return mDesiredHeight;
}
+ /**
+ * @return whether this bubble should auto expand when it is posted.
+ *
+ * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
+ */
+ public boolean getAutoExpandBubble() {
+ return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
+ }
+
+ /**
+ * @return whether this bubble should suppress the initial notification when it is posted.
+ *
+ * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean)
+ */
+ public boolean getSuppressInitialNotification() {
+ return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0;
+ }
+
public static final Parcelable.Creator<BubbleMetadata> CREATOR =
new Parcelable.Creator<BubbleMetadata>() {
@@ -8469,6 +8515,11 @@ public class Notification implements Parcelable
out.writeCharSequence(mTitle);
mIcon.writeToParcel(out, 0);
out.writeInt(mDesiredHeight);
+ out.writeInt(mFlags);
+ }
+
+ private void setFlags(int flags) {
+ mFlags = flags;
}
/**
@@ -8480,6 +8531,7 @@ public class Notification implements Parcelable
private CharSequence mTitle;
private Icon mIcon;
private int mDesiredHeight;
+ private int mFlags;
/**
* Constructs a new builder object.
@@ -8539,6 +8591,39 @@ public class Notification implements Parcelable
}
/**
+ * If set and the app creating the bubble is in the foreground, the bubble will be
+ * posted in its expanded state, with the contents of {@link #getIntent()} in a
+ * floating window.
+ *
+ * <p>If the app creating the bubble is not in the foreground this flag has no effect.
+ * </p>
+ *
+ * <p>Generally this flag should only be set if the user has performed an action to
+ * request or create a bubble.</p>
+ */
+ public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
+ setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
+ return this;
+ }
+
+ /**
+ * If set and the app creating the bubble is in the foreground, the bubble will be
+ * posted <b>without</b> the associated notification in the notification shade.
+ * Subsequent update notifications to this bubble will post a notification in the shade.
+ *
+ * <p>If the app creating the bubble is not in the foreground this flag has no effect.
+ * </p>
+ *
+ * <p>Generally this flag should only be set if the user has performed an action to
+ * request or create a bubble.</p>
+ */
+ public BubbleMetadata.Builder setSuppressInitialNotification(
+ boolean shouldSupressNotif) {
+ setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif);
+ return this;
+ }
+
+ /**
* Creates the {@link BubbleMetadata} defined by this builder.
* <p>Will throw {@link IllegalStateException} if required fields have not been set
* on this builder.</p>
@@ -8553,7 +8638,22 @@ public class Notification implements Parcelable
if (mIcon == null) {
throw new IllegalStateException("Must supply an icon for the bubble");
}
- return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight);
+ BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon,
+ mDesiredHeight);
+ data.setFlags(mFlags);
+ return data;
+ }
+
+ /**
+ * @hide
+ */
+ public BubbleMetadata.Builder setFlag(int mask, boolean value) {
+ if (value) {
+ mFlags |= mask;
+ } else {
+ mFlags &= ~mask;
+ }
+ return this;
}
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 43614feb28a4..c4b4b4070ce7 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1137,9 +1137,6 @@ public class NotificationManager {
}
}
- /**
- * @hide
- */
public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 9dcd1228522b..9b66c928abc0 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -73,6 +73,11 @@ public final class StatsManager {
*/
public static final String EXTRA_STATS_DIMENSIONS_VALUE =
"android.app.extra.STATS_DIMENSIONS_VALUE";
+ /**
+ * Long array extra of the active configs for the uid that added those configs.
+ */
+ public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS =
+ "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
/**
* Broadcast Action: Statsd has started.
@@ -274,6 +279,43 @@ public final class StatsManager {
}
}
+ /**
+ * Registers the operation that is called whenever there is a change in which configs are
+ * active. This must be called each time statsd starts. This operation allows
+ * statsd to inform clients that they should pull data of the configs that are currently
+ * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs
+ * that are active and stop pulling data of configs that are no longer active.
+ *
+ * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+ * associated with the given subscriberId. May be null, in which case
+ * it removes any associated pending intent for this client.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ */
+ @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
+ public void setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent)
+ throws StatsUnavailableException {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (pendingIntent == null) {
+ service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
+ } else {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ service.setActiveConfigsChangedOperation(intentSender,
+ mContext.getOpPackageName());
+ }
+
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "Failed to connect to statsd when registering active configs listener.");
+ throw new StatsUnavailableException("could not connect", e);
+ } catch (SecurityException e) {
+ throw new StatsUnavailableException(e.getMessage(), e);
+ }
+ }
+ }
+
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 55a3acb16b11..2514eee09da0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3291,7 +3291,7 @@ public class DevicePolicyManager {
*/
public int getPasswordMaximumLength(int quality) {
PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION)) {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
return 0;
}
// Kind-of arbitrary.
@@ -3980,6 +3980,10 @@ public class DevicePolicyManager {
*/
public static final int WIPE_EUICC = 0x0004;
+ /**
+ * Flag for {@link #wipeData(int)}: won't show reason for wiping to the user.
+ */
+ public static final int WIPE_SILENTLY = 0x0008;
/**
* Ask that all user data be wiped. If called as a secondary user, the user will be removed and
@@ -3991,9 +3995,10 @@ public class DevicePolicyManager {
* be able to call this method; if it has not, a security exception will be thrown.
*
* @param flags Bit mask of additional options: currently supported flags are
- * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA},
+ * {@link #WIPE_EUICC} and {@link #WIPE_SILENTLY}.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
*/
public void wipeData(int flags) {
throwIfParentInstance("wipeData");
@@ -4013,16 +4018,21 @@ public class DevicePolicyManager {
* be able to call this method; if it has not, a security exception will be thrown.
*
* @param flags Bit mask of additional options: currently supported flags are
- * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+ * {@link #WIPE_EXTERNAL_STORAGE}, {@link #WIPE_RESET_PROTECTION_DATA} and
+ * {@link #WIPE_EUICC}.
* @param reason a string that contains the reason for wiping data, which can be
- * presented to the user. If the string is null or empty, user won't be notified.
+ * presented to the user.
* @throws SecurityException if the calling application does not own an active administrator
- * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
- * @throws IllegalArgumentException if the input reason string is null or empty.
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+ * @throws IllegalArgumentException if the input reason string is null or empty, or if
+ * {@link #WIPE_SILENTLY} is set.
*/
- public void wipeData(int flags, CharSequence reason) {
+ public void wipeData(int flags, @NonNull CharSequence reason) {
throwIfParentInstance("wipeData");
- wipeDataInternal(flags, reason != null ? reason.toString() : null);
+ Preconditions.checkNotNull(reason, "reason string is null");
+ Preconditions.checkStringNotEmpty(reason, "reason string is empty");
+ Preconditions.checkArgument((flags & WIPE_SILENTLY) == 0, "WIPE_SILENTLY cannot be set");
+ wipeDataInternal(flags, reason.toString());
}
/**
@@ -4033,7 +4043,7 @@ public class DevicePolicyManager {
* @see #wipeData(int, CharSequence)
* @hide
*/
- private void wipeDataInternal(int flags, String wipeReasonForUser) {
+ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
if (mService != null) {
try {
mService.wipeDataWithReason(flags, wipeReasonForUser);
@@ -10428,76 +10438,53 @@ public class DevicePolicyManager {
}
/**
- * Whitelists a package that is allowed to access cross profile calendar APIs.
+ * Whitelists a set of packages that are allowed to access cross-profile calendar APIs.
*
* <p>Called by a profile owner of a managed profile.
*
- * @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName name of the package to be whitelisted.
- * @throws SecurityException if {@code admin} is not a profile owner.
- *
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
- * @see #getCrossProfileCalendarPackages(ComponentName)
- */
- public void addCrossProfileCalendarPackage(@NonNull ComponentName admin,
- @NonNull String packageName) {
- throwIfParentInstance("addCrossProfileCalendarPackage");
- if (mService != null) {
- try {
- mService.addCrossProfileCalendarPackage(admin, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
-
- /**
- * Removes a package that was allowed to access cross profile calendar APIs
- * from the whitelist.
- *
- * <p>Called by a profile owner of a managed profile.
+ * <p>Calling with a null value for the set disables the restriction so that all packages
+ * are allowed to access cross-profile calendar APIs. Calling with an empty set disallows
+ * all packages from accessing cross-profile calendar APIs. If this method isn't called,
+ * no package will be allowed to access cross-profile calendar APIs by default.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName name of the package to be removed from the whitelist.
- * @return {@code true} if the package is successfully removed from the whitelist,
- * {@code false} otherwise.
+ * @param packageNames set of packages to be whitelisted.
* @throws SecurityException if {@code admin} is not a profile owner.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
* @see #getCrossProfileCalendarPackages(ComponentName)
*/
- public boolean removeCrossProfileCalendarPackage(@NonNull ComponentName admin,
- @NonNull String packageName) {
- throwIfParentInstance("removeCrossProfileCalendarPackage");
+ public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
+ @Nullable Set<String> packageNames) {
+ throwIfParentInstance("setCrossProfileCalendarPackages");
if (mService != null) {
try {
- return mService.removeCrossProfileCalendarPackage(admin, packageName);
+ mService.setCrossProfileCalendarPackages(admin, packageNames == null ? null
+ : new ArrayList<>(packageNames));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- return false;
}
/**
- * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+ * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
*
* <p>Called by a profile owner of a managed profile.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
* @return the set of names of packages that were previously whitelisted via
- * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+ * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
* empty set if none have been whitelisted.
* @throws SecurityException if {@code admin} is not a profile owner.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
*/
- public @NonNull Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
+ public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getCrossProfileCalendarPackages(admin));
+ final List<String> packageNames = mService.getCrossProfileCalendarPackages(admin);
+ return packageNames == null ? null : new ArraySet<>(packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10506,22 +10493,21 @@ public class DevicePolicyManager {
}
/**
- * Returns if a package is whitelisted to access cross profile calendar APIs.
+ * Returns if a package is whitelisted to access cross-profile calendar APIs.
*
* <p>To query for a specific user, use
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
* that user, and get a {@link DevicePolicyManager} from this context.
*
* @param packageName the name of the package
- * @return {@code true} if the package is whitelisted to access cross profile calendar APIs.
+ * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs.
* {@code false} otherwise.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
- public @NonNull boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
+ public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
throwIfParentInstance("isPackageAllowedToAccessCalendar");
if (mService != null) {
try {
@@ -10535,27 +10521,27 @@ public class DevicePolicyManager {
}
/**
- * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+ * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
*
* <p>To query for a specific user, use
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
* that user, and get a {@link DevicePolicyManager} from this context.
*
* @return the set of names of packages that were previously whitelisted via
- * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+ * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
* empty set if none have been whitelisted.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
- public @NonNull Set<String> getCrossProfileCalendarPackages() {
+ public @Nullable Set<String> getCrossProfileCalendarPackages() {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getCrossProfileCalendarPackagesForUser(
- myUserId()));
+ final List<String> packageNames = mService.getCrossProfileCalendarPackagesForUser(
+ myUserId());
+ return packageNames == null ? null : new ArraySet<>(packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 568becfcdd1a..1751a91caf1a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -424,8 +424,7 @@ interface IDevicePolicyManager {
void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener);
- void addCrossProfileCalendarPackage(in ComponentName admin, String packageName);
- boolean removeCrossProfileCalendarPackage(in ComponentName admin, String packageName);
+ void setCrossProfileCalendarPackages(in ComponentName admin, in List<String> packageNames);
List<String> getCrossProfileCalendarPackages(in ComponentName admin);
boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
diff --git a/core/java/android/app/contentsuggestions/ClassificationsRequest.java b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
index 3f715186abfb..9bb39e599135 100644
--- a/core/java/android/app/contentsuggestions/ClassificationsRequest.java
+++ b/core/java/android/app/contentsuggestions/ClassificationsRequest.java
@@ -26,6 +26,11 @@ import android.os.Parcelable;
import java.util.List;
/**
+ * Request object used when asking {@link ContentSuggestionsManager} to classify content selections.
+ *
+ * <p>The request contains a list of {@link ContentSelection} objects to be classified along with
+ * implementation specific extras.
+ *
* @hide
*/
@SystemApi
@@ -44,14 +49,14 @@ public final class ClassificationsRequest implements Parcelable {
/**
* Return request selections.
*/
- public List<ContentSelection> getSelections() {
+ public @NonNull List<ContentSelection> getSelections() {
return mSelections;
}
/**
- * Return the request extras.
+ * Return the request extras or {@code null} if there are none.
*/
- public Bundle getExtras() {
+ public @Nullable Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/contentsuggestions/ContentClassification.java b/core/java/android/app/contentsuggestions/ContentClassification.java
index f18520f1053c..2a00b406f814 100644
--- a/core/java/android/app/contentsuggestions/ContentClassification.java
+++ b/core/java/android/app/contentsuggestions/ContentClassification.java
@@ -23,6 +23,9 @@ import android.os.Parcel;
import android.os.Parcelable;
/**
+ * Represents the classification of a content suggestion. The result of a
+ * {@link ClassificationsRequest} to {@link ContentSuggestionsManager}.
+ *
* @hide
*/
@SystemApi
@@ -32,6 +35,12 @@ public final class ContentClassification implements Parcelable {
@NonNull
private final Bundle mExtras;
+ /**
+ * Default constructor.
+ *
+ * @param classificationId implementation specific id for the selection / classification.
+ * @param extras containing the classification data.
+ */
public ContentClassification(@NonNull String classificationId, @NonNull Bundle extras) {
mClassificationId = classificationId;
mExtras = extras;
@@ -40,14 +49,14 @@ public final class ContentClassification implements Parcelable {
/**
* Return the classification id.
*/
- public String getId() {
+ public @NonNull String getId() {
return mClassificationId;
}
/**
* Return the classification extras.
*/
- public Bundle getExtras() {
+ public @NonNull Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/contentsuggestions/ContentSelection.java b/core/java/android/app/contentsuggestions/ContentSelection.java
index a8917f782e83..16b4f3f6456d 100644
--- a/core/java/android/app/contentsuggestions/ContentSelection.java
+++ b/core/java/android/app/contentsuggestions/ContentSelection.java
@@ -23,6 +23,9 @@ import android.os.Parcel;
import android.os.Parcelable;
/**
+ * Represents a suggested selection within a set of on screen content. The result of a
+ * {@link SelectionsRequest} to {@link ContentSuggestionsManager}.
+ *
* @hide
*/
@SystemApi
@@ -32,6 +35,12 @@ public final class ContentSelection implements Parcelable {
@NonNull
private final Bundle mExtras;
+ /**
+ * Default constructor.
+ *
+ * @param selectionId implementation specific id for the selection.
+ * @param extras containing the data that represents the selection.
+ */
public ContentSelection(@NonNull String selectionId, @NonNull Bundle extras) {
mSelectionId = selectionId;
mExtras = extras;
@@ -40,14 +49,14 @@ public final class ContentSelection implements Parcelable {
/**
* Return the selection id.
*/
- public String getId() {
+ public @NonNull String getId() {
return mSelectionId;
}
/**
* Return the selection extras.
*/
- public Bundle getExtras() {
+ public @NonNull Bundle getExtras() {
return mExtras;
}
diff --git a/core/java/android/app/contentsuggestions/SelectionsRequest.java b/core/java/android/app/contentsuggestions/SelectionsRequest.java
index 16f3e6b35aa4..e3c8bc5c7e9e 100644
--- a/core/java/android/app/contentsuggestions/SelectionsRequest.java
+++ b/core/java/android/app/contentsuggestions/SelectionsRequest.java
@@ -25,6 +25,12 @@ import android.os.Parcel;
import android.os.Parcelable;
/**
+ * The request object used to request content selections from {@link ContentSuggestionsManager}.
+ *
+ * <p>Selections are requested for a given taskId as specified by
+ * {@link android.app.ActivityManager} and optionally take an interest point that specifies the
+ * point on the screen that should be considered as the most important.
+ *
* @hide
*/
@SystemApi
@@ -49,16 +55,17 @@ public final class SelectionsRequest implements Parcelable {
}
/**
- * Return the request point of interest.
+ * Return the request point of interest or {@code null} if there is no point of interest for
+ * this request.
*/
- public Point getInterestPoint() {
+ public @Nullable Point getInterestPoint() {
return mInterestPoint;
}
/**
- * Return the request extras.
+ * Return the request extras or {@code null} if there aren't any.
*/
- public Bundle getExtras() {
+ public @Nullable Bundle getExtras() {
return mExtras;
}
@@ -99,6 +106,11 @@ public final class SelectionsRequest implements Parcelable {
private Point mInterestPoint;
private Bundle mExtras;
+ /**
+ * Default constructor.
+ *
+ * @param taskId of the type used by {@link android.app.ActivityManager}
+ */
public Builder(int taskId) {
mTaskId = taskId;
}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index a6abe0b30f79..ddd531339d39 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -172,6 +172,15 @@ public final class RoleManager {
public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
/**
+ * The name of the assistant app role.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
+
+ /**
* The action used to request user approval of a role for an application.
*
* @hide
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index d2934b9f5a21..b1500c193820 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,6 +55,9 @@ interface IUsageStatsManager {
long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
in PendingIntent sessionEndCallbackIntent, String callingPackage);
void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
+ void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs,
+ in PendingIntent callback, String callingPackage);
+ void unregisterAppUsageLimitObserver(int observerId, String callingPackage);
void reportUsageStart(in IBinder activity, String token, String callingPackage);
void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d2de8872c1bd..51397a243420 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -619,7 +619,7 @@ public final class UsageStatsManager {
* @param timeLimit The total time the set of apps can be in the foreground before the
* callbackIntent is delivered. Must be at least one minute.
* @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
- * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
+ * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
* exceeded by the group of apps. The delivered Intent will also contain
* the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
* {@link #EXTRA_TIME_USED}. Cannot be null.
@@ -682,14 +682,14 @@ public final class UsageStatsManager {
* @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
* Cannot be null.
* @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
- * time limit is exceeded by the group of apps. The delivered
- * Intent will also contain the extras {@link
+ * usage limit is exceeded by the group of apps. The
+ * delivered Intent will also contain the extras {@link
* #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
* #EXTRA_TIME_USED}. Cannot be null.
* @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the
- * session has ended after the time limit has been exceeded. The
- * session is considered at its end after the {@code observed}
- * usage has stopped and an additional {@code
+ * session has ended after the usage limit has been exceeded.
+ * The session is considered at its end after the {@code
+ * observed} usage has stopped and an additional {@code
* sessionThresholdTime} has passed. The delivered Intent will
* also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
* #EXTRA_TIME_USED}. Can be null.
@@ -736,6 +736,74 @@ public final class UsageStatsManager {
}
/**
+ * Register a usage limit observer that receives a callback on the provided intent when the
+ * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the
+ * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting
+ * package's name and a token that the calling app will use, separated by the forward slash
+ * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+ * <p>
+ * Registering an {@code observerId} that was already registered will override the previous one.
+ * No more than 1000 unique {@code observerId} may be registered by a single uid
+ * at any one time.
+ * A limit may be unregistered via {@link #unregisterAppUsageLimitObserver}
+ * <p>
+ * This method is similar to {@link #registerAppUsageObserver}, but the usage limit set here
+ * will be visible to the launcher so that it can report the limit to the user and how much
+ * of it is remaining.
+ * @see android.content.pm.LauncherApps#getAppUsageLimit
+ *
+ * @param observerId A unique id associated with the group of apps to be monitored. There can
+ * be multiple groups with common packages and different time limits.
+ * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+ * null and must include at least one package or token.
+ * @param timeLimit The total time the set of apps can be in the foreground before the
+ * callbackIntent is delivered. Must be at least one minute.
+ * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+ * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
+ * exceeded by the group of apps. The delivered Intent will also contain
+ * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
+ * {@link #EXTRA_TIME_USED}. Cannot be null.
+ * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+ * permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SUSPEND_APPS,
+ android.Manifest.permission.OBSERVE_APP_USAGE})
+ public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
+ long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
+ try {
+ mService.registerAppUsageLimitObserver(observerId, observedEntities,
+ timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister the app usage limit observer specified by the {@code observerId}.
+ * This will only apply to any observer registered by this application. Unregistering
+ * an observer that was already unregistered or never registered will have no effect.
+ *
+ * @param observerId The id of the observer that was previously registered.
+ * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+ * permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SUSPEND_APPS,
+ android.Manifest.permission.OBSERVE_APP_USAGE})
+ public void unregisterAppUsageLimitObserver(int observerId) {
+ try {
+ mService.unregisterAppUsageLimitObserver(observerId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Report usage associated with a particular {@code token} has started. Tokens are app defined
* strings used to represent usage of in-app features. Apps with the {@link
* android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
@@ -743,6 +811,7 @@ public final class UsageStatsManager {
* and usage will be considered stopped if the activity stops or crashes.
* @see #registerAppUsageObserver
* @see #registerUsageSessionObserver
+ * @see #registerAppUsageLimitObserver
*
* @param activity The activity {@code token} is associated with.
* @param token The token to report usage against.
@@ -766,6 +835,7 @@ public final class UsageStatsManager {
* {@code activity} and usage will be considered stopped if the activity stops or crashes.
* @see #registerAppUsageObserver
* @see #registerUsageSessionObserver
+ * @see #registerAppUsageLimitObserver
*
* @param activity The activity {@code token} is associated with.
* @param token The token to report usage against.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index d2d0cf9ca90b..3d3c03ae3daa 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -20,6 +20,7 @@ import android.annotation.UserIdInt;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.os.UserHandle;
import java.util.List;
import java.util.Set;
@@ -270,4 +271,40 @@ public abstract class UsageStatsManagerInternal {
* @param userId which user the app is associated with
*/
public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
+
+ /**
+ * Returns an object describing the app usage limit for the given package which was set via
+ * {@link UsageStatsManager#registerAppUsageLimitObserver}.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user associated with the limit
+ * @return an {@link AppUsageLimitData} object describing the app time limit containing
+ * the given package, with the smallest time remaining.
+ */
+ public abstract AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user);
+
+ /** A class which is used to share the usage limit data for an app or a group of apps. */
+ public static class AppUsageLimitData {
+ private final boolean mGroupLimit;
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+ this.mGroupLimit = groupLimit;
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ public boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 171c2f5b1a08..c4bf1ebd1a6f 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -434,7 +434,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* {@inheritDoc}
*/
@Override
- public int getConnectionState(BluetoothDevice device) {
+ public @BtProfileState int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
try {
mServiceLock.readLock().lock();
@@ -689,7 +689,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* @hide
*/
@UnsupportedAppUsage
- public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+ public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
try {
mServiceLock.readLock().lock();
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 78560d2de420..2cb7b2d3c7e9 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -16,6 +16,7 @@
package android.bluetooth;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,7 +43,7 @@ public final class BluetoothCodecStatus implements Parcelable {
public static final String EXTRA_CODEC_STATUS =
"android.bluetooth.codec.extra.CODEC_STATUS";
- private final BluetoothCodecConfig mCodecConfig;
+ private final @Nullable BluetoothCodecConfig mCodecConfig;
private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
@@ -140,7 +141,7 @@ public final class BluetoothCodecStatus implements Parcelable {
* @return the current codec configuration
*/
@UnsupportedAppUsage
- public BluetoothCodecConfig getCodecConfig() {
+ public @Nullable BluetoothCodecConfig getCodecConfig() {
return mCodecConfig;
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 17cf702bbdea..4d8dc35d7148 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -532,6 +532,28 @@ public final class BluetoothDevice implements Parcelable {
"android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL";
/**
+ * Intent to broadcast silence mode changed.
+ * Alway contains the extra field {@link #EXTRA_DEVICE}
+ * Alway contains the extra field {@link #EXTRA_SILENCE_ENABLED}
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_SILENCE_MODE_CHANGED =
+ "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+
+ /**
+ * Used as an extra field in {@link #ACTION_SILENCE_MODE_CHANGED} intent,
+ * contains whether device is in silence mode as boolean.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_SILENCE_ENABLED =
+ "android.bluetooth.device.extra.SILENCE_ENABLED";
+
+ /**
* Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent.
*
* @hide
@@ -1592,6 +1614,70 @@ public final class BluetoothDevice implements Parcelable {
}
/**
+ * Set the Bluetooth device silence mode.
+ *
+ * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice}
+ * is an active device (for A2DP or HFP), the active device for that profile
+ * will be set to null.
+ * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP
+ * active device is null, the {@link BluetoothDevice} will be set as the
+ * active device for that profile.
+ * If the {@link BluetoothDevice} is disconnected, it exits silence mode.
+ * If the {@link BluetoothDevice} is set as the active device for A2DP or
+ * HFP, while silence mode is enabled, then the device will exit silence mode.
+ * If the {@link BluetoothDevice} is in silence mode, AVRCP position change
+ * event and HFP AG indicators will be disabled.
+ * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot
+ * enter silence mode.
+ *
+ * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+ *
+ * @param silence true to enter silence mode, false to exit
+ * @return true on success, false on error.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setSilenceMode(boolean silence) {
+ final IBluetooth service = sService;
+ if (service == null) {
+ return false;
+ }
+ try {
+ if (getSilenceMode() == silence) {
+ return true;
+ }
+ return service.setSilenceMode(this, silence);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setSilenceMode fail", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get the device silence mode status
+ *
+ * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+ *
+ * @return true on device in silence mode, otherwise false.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean getSilenceMode() {
+ final IBluetooth service = sService;
+ if (service == null) {
+ return false;
+ }
+ try {
+ return service.getSilenceMode(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getSilenceMode fail", e);
+ return false;
+ }
+ }
+
+ /**
* Sets whether the phonebook access is allowed to this device.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
*
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index b8670dbeadad..ef775967f8a7 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -18,11 +18,14 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -60,6 +63,16 @@ public interface BluetoothProfile {
/** The profile is in disconnecting state */
int STATE_DISCONNECTING = 3;
+ /** @hide */
+ @IntDef({
+ STATE_DISCONNECTED,
+ STATE_CONNECTING,
+ STATE_CONNECTED,
+ STATE_DISCONNECTING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BtProfileState {}
+
/**
* Headset and Handsfree profile
*/
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 6704a45fb07a..47a4a2dca73d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -87,6 +87,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* <p>For more information about using a ContentResolver with content providers, read the
* <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>
* developer guide.</p>
+ * </div>
*/
public abstract class ContentResolver implements ContentInterface {
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b46203c7a682..22f73dbd4644 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2375,8 +2375,7 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PACKAGE_ENABLE_ROLLBACK =
"android.intent.action.PACKAGE_ENABLE_ROLLBACK";
/**
- * Broadcast Action: An existing version of an application package has been
- * rolled back to a previous version.
+ * Broadcast Action: A rollback has been committed.
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
@@ -2385,8 +2384,8 @@ public class Intent implements Parcelable, Cloneable {
*/
@SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED =
- "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
+ public static final String ACTION_ROLLBACK_COMMITTED =
+ "android.intent.action.ROLLBACK_COMMITTED";
/**
* @hide
* Broadcast Action: Ask system services if there is any reason to
@@ -3047,6 +3046,13 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_SCANNER_SCAN_FILE = "android.intent.action.MEDIA_SCANNER_SCAN_FILE";
+ /**
+ * Broadcast Action: Request the media scanner to scan a storage volume and add it to the media database.
+ * The path to the storage volume is contained in the Intent.mData field.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_MEDIA_SCANNER_SCAN_VOLUME = "android.intent.action.MEDIA_SCANNER_SCAN_VOLUME";
+
/**
* Broadcast Action: The "Media Button" was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
@@ -9969,9 +9975,21 @@ public class Intent implements Parcelable, Cloneable {
}
/** @hide */
+ public void writeToProto(ProtoOutputStream proto) {
+ // Same input parameters that toString() gives to toShortString().
+ writeToProtoWithoutFieldId(proto, true, true, true, false);
+ }
+
+ /** @hide */
public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
boolean extras, boolean clip) {
long token = proto.start(fieldId);
+ writeToProtoWithoutFieldId(proto, secure, comp, extras, clip);
+ proto.end(token);
+ }
+
+ private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp,
+ boolean extras, boolean clip) {
if (mAction != null) {
proto.write(IntentProto.ACTION, mAction);
}
@@ -10016,7 +10034,6 @@ public class Intent implements Parcelable, Cloneable {
if (mSelector != null) {
proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
}
- proto.end(token);
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 576466f21bcb..5d6d1444eaf3 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1955,6 +1955,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
}
+ /** @hide */
+ public boolean isCodeIntegrityPreferred() {
+ return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0;
+ }
+
/**
* Returns whether or not this application was installed as a virtual preload.
*/
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index db2b6fd235d3..d1bc37791d40 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -23,6 +23,7 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.LauncherApps;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -56,6 +57,9 @@ interface ILauncherApps {
ApplicationInfo getApplicationInfo(
String callingPackage, String packageName, int flags, in UserHandle user);
+ LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName,
+ in UserHandle user);
+
ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c702b16c97f6..276853d3b860 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -76,4 +76,6 @@ interface IShortcutService {
// System API used by framework's ShareSheet (ChooserActivity)
ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId);
+
+ boolean hasShareTargets(String packageName, String packageToCheck, int userId);
} \ No newline at end of file
diff --git a/core/java/android/content/pm/LauncherApps.aidl b/core/java/android/content/pm/LauncherApps.aidl
new file mode 100644
index 000000000000..1d98ad1abd32
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable LauncherApps.AppUsageLimit;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 766c5660012b..89630e15972e 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -758,6 +758,27 @@ public class LauncherApps {
}
/**
+ * Returns an object describing the app usage limit for the given package.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user of the package
+ *
+ * @return an {@link AppUsageLimit} object describing the app time limit containing
+ * the given package with the smallest time remaining, or {@code null} if none exist.
+ * @throws SecurityException when the caller is not the active launcher.
+ */
+ @Nullable
+ public LauncherApps.AppUsageLimit getAppUsageLimit(String packageName, UserHandle user) {
+ try {
+ return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the activity exists and it enabled for a profile.
*
* @param component The activity to check.
@@ -1632,4 +1653,86 @@ public class LauncherApps {
return 0;
}
}
+
+ /**
+ * A class that encapsulates information about the usage limit set for an app or
+ * a group of apps.
+ *
+ * <p>The launcher can query specifics about the usage limit such as if it is a group limit,
+ * how much usage time the limit has, and how much of the total usage time is remaining
+ * via the APIs available in this class.
+ *
+ * @see #getAppUsageLimit(String, UserHandle)
+ */
+ public static final class AppUsageLimit implements Parcelable {
+ private final boolean mGroupLimit;
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ /** @hide */
+ public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+ this.mGroupLimit = groupLimit;
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ /**
+ * Returns whether this limit refers to a group of apps.
+ *
+ * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise.
+ * @hide
+ */
+ public boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+
+ /**
+ * Returns the total usage limit in milliseconds set for an app or a group of apps.
+ *
+ * @return the total usage limit in milliseconds
+ */
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+
+ /**
+ * Returns the usage remaining in milliseconds for an app or the group of apps
+ * this limit refers to.
+ *
+ * @return the usage remaining in milliseconds
+ */
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+
+ private AppUsageLimit(Parcel source) {
+ mGroupLimit = source.readBoolean();
+ mTotalUsageLimit = source.readLong();
+ mUsageRemaining = source.readLong();
+ }
+
+ public static final Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
+ @Override
+ public AppUsageLimit createFromParcel(Parcel source) {
+ return new AppUsageLimit(source);
+ }
+
+ @Override
+ public AppUsageLimit[] newArray(int size) {
+ return new AppUsageLimit[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mGroupLimit);
+ dest.writeLong(mTotalUsageLimit);
+ dest.writeLong(mUsageRemaining);
+ }
+ }
}
diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
index 81e4105febee..7790067b03de 100644
--- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
+++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
@@ -25,12 +25,6 @@ import com.android.internal.annotations.VisibleForTesting;
* Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is
* included by default.
*
- * <p>This is separated out so that it can be conditionally included at build time depending on
- * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at
- * build time, and remove org.apache.http.legacy from the bootclasspath pass
- * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included
- * and the
- *
* @hide
*/
@VisibleForTesting
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 03eefedd2b30..b19196a9b636 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -45,13 +45,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater {
static {
final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
- // Attempt to load and add the optional updater that will only be available when
- // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that
- // will remove any references to org.apache.http.library from the package so that it does
- // not try and load the library when it is on the bootclasspath.
- boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters,
- "android.content.pm.OrgApacheHttpLegacyUpdater",
- RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new);
+ // Automatically add the org.apache.http.legacy library to the app classpath if the app
+ // targets < P.
+ packageUpdaters.add(new OrgApacheHttpLegacyUpdater());
packageUpdaters.add(new AndroidHidlUpdater());
@@ -70,7 +66,7 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater {
PackageSharedLibraryUpdater[] updaterArray = packageUpdaters
.toArray(new PackageSharedLibraryUpdater[0]);
INSTANCE = new PackageBackwardCompatibility(
- bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray);
+ bootClassPathContainsATB, updaterArray);
}
/**
@@ -116,15 +112,12 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater {
return INSTANCE;
}
- private final boolean mBootClassPathContainsOAHL;
-
private final boolean mBootClassPathContainsATB;
private final PackageSharedLibraryUpdater[] mPackageUpdaters;
- public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL,
+ public PackageBackwardCompatibility(
boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) {
- this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL;
this.mBootClassPathContainsATB = bootClassPathContainsATB;
this.mPackageUpdaters = packageUpdaters;
}
@@ -148,14 +141,6 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater {
}
/**
- * True if the org.apache.http.legacy is on the bootclasspath, false otherwise.
- */
- @VisibleForTesting
- public static boolean bootClassPathContainsOAHL() {
- return INSTANCE.mBootClassPathContainsOAHL;
- }
-
- /**
* True if the android.test.base is on the bootclasspath, false otherwise.
*/
@VisibleForTesting
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 94b7c4538c51..2dc014c45fad 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1543,6 +1543,13 @@ public class PackageInstaller {
this.isStaged = true;
}
+ /**
+ * Set this session to be installing an APEX package.
+ */
+ public void setInstallAsApex() {
+ installFlags |= PackageManager.INSTALL_APEX;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
@@ -1703,6 +1710,7 @@ public class PackageInstaller {
/** {@hide} */
public boolean isSessionFailed;
private int mStagedSessionErrorCode;
+ private String mStagedSessionErrorMessage;
/** {@hide} */
@UnsupportedAppUsage
@@ -1742,6 +1750,7 @@ public class PackageInstaller {
isSessionReady = source.readBoolean();
isSessionFailed = source.readBoolean();
mStagedSessionErrorCode = source.readInt();
+ mStagedSessionErrorMessage = source.readString();
}
/**
@@ -2059,9 +2068,19 @@ public class PackageInstaller {
return mStagedSessionErrorCode;
}
+ /**
+ * Text description of the error code returned by {@code getStagedSessionErrorCode}, or
+ * empty string if no error was encountered.
+ */
+ public String getStagedSessionErrorMessage() {
+ return mStagedSessionErrorMessage;
+ }
+
/** {@hide} */
- public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode) {
+ public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode,
+ String errorMessage) {
mStagedSessionErrorCode = errorCode;
+ mStagedSessionErrorMessage = errorMessage;
}
@Override
@@ -2099,6 +2118,7 @@ public class PackageInstaller {
dest.writeBoolean(isSessionReady);
dest.writeBoolean(isSessionFailed);
dest.writeInt(mStagedSessionErrorCode);
+ dest.writeString(mStagedSessionErrorMessage);
}
public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d5636d5fc91f..eb59cfc0fc4b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8512,6 +8512,7 @@ public class PackageParser {
collectCerts ? PackageParser.PARSE_COLLECT_CERTIFICATES : 0);
pi.packageName = apk.packageName;
+ ai.packageName = apk.packageName;
pi.setLongVersionCode(apk.getLongVersionCode());
ai.setVersionCode(apk.getLongVersionCode());
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 4f7acd96aa6b..849fd03eacb3 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -635,4 +635,21 @@ public class ShortcutManager {
}
};
}
+
+ /**
+ * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share
+ * target definitions in it's resources.
+ *
+ * @param packageName Package to check for share targets.
+ * @return True if the package has any share target definitions, False otherwise.
+ * @hide
+ */
+ public boolean hasShareTargets(@NonNull String packageName) {
+ try {
+ return mService.hasShareTargets(mContext.getPackageName(), packageName,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 2faf3adb34b6..4f4c34b88cbd 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -27,5 +27,4 @@ import android.os.RemoteCallback;
*/
oneway interface IRuntimePermissionPresenter {
void getAppPermissions(String packageName, in RemoteCallback callback);
- void revokeRuntimePermission(String packageName, String permissionName);
}
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 420bcb69e0c4..63d75a097d24 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -17,17 +17,13 @@
package android.content.rollback;
import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
import android.content.rollback.RollbackInfo;
import android.content.IntentSender;
/** {@hide} */
interface IRollbackManager {
- RollbackInfo getAvailableRollback(String packageName);
-
- StringParceledListSlice getPackagesWithAvailableRollbacks();
-
+ ParceledListSlice getAvailableRollbacks();
ParceledListSlice getRecentlyExecutedRollbacks();
void executeRollback(in RollbackInfo rollback, String callerPackageName,
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 0803a7c1d651..8532b5a4844e 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -20,6 +20,8 @@ import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.List;
+
/**
* Information about a set of packages that can be, or already have been
* rolled back together.
@@ -34,25 +36,20 @@ public final class RollbackInfo implements Parcelable {
*/
private final int mRollbackId;
- /**
- * The package that needs to be rolled back.
- */
- public final PackageRollbackInfo targetPackage;
+ private final List<PackageRollbackInfo> mPackages;
- // TODO: Add a list of additional packages rolled back due to atomic
- // install dependencies when rollback of atomic installs is supported.
// TODO: Add a flag to indicate if reboot is required, when rollback of
// staged installs is supported.
/** @hide */
- public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+ public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) {
this.mRollbackId = rollbackId;
- this.targetPackage = targetPackage;
+ this.mPackages = packages;
}
private RollbackInfo(Parcel in) {
mRollbackId = in.readInt();
- targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+ mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
}
/**
@@ -62,6 +59,13 @@ public final class RollbackInfo implements Parcelable {
return mRollbackId;
}
+ /**
+ * Returns the list of package that are rolled back.
+ */
+ public List<PackageRollbackInfo> getPackages() {
+ return mPackages;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -70,7 +74,7 @@ public final class RollbackInfo implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mRollbackId);
- targetPackage.writeToParcel(out, flags);
+ out.writeTypedList(mPackages);
}
public static final Parcelable.Creator<RollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index c1c0bc1d3e07..2566ac562e11 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -17,7 +17,6 @@
package android.content.rollback;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -50,55 +49,26 @@ public final class RollbackManager {
}
/**
- * Returns the rollback currently available to be executed for the given
- * package.
- * <p>
- * The returned RollbackInfo describes what packages would be rolled back,
- * including package version codes before and after rollback. The rollback
- * can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}.
- * <p>
- * TODO: What if there is no package installed on device for packageName?
+ * Returns a list of all currently available rollbacks.
*
- * @param packageName name of the package to get the availble RollbackInfo for.
- * @return the rollback available for the package, or null if no rollback
- * is available for the package.
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) {
+ public List<RollbackInfo> getAvailableRollbacks() {
try {
- return mBinder.getAvailableRollback(packageName);
+ return mBinder.getAvailableRollbacks().getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Gets the names of packages that are available for rollback.
- * Call {@link #getAvailableRollback(String)} to get more information
- * about the rollback available for a particular package.
- *
- * @return the names of packages that are available for rollback.
- * @throws SecurityException if the caller does not have the
- * MANAGE_ROLLBACKS permission.
- */
- @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public @NonNull List<String> getPackagesWithAvailableRollbacks() {
- try {
- return mBinder.getPackagesWithAvailableRollbacks().getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
-
- /**
- * Gets the list of all recently executed rollbacks.
+ * Gets the list of all recently committed rollbacks.
* This is for the purposes of preventing re-install of a bad version of a
- * package.
+ * package and monitoring the status of a staged rollback.
* <p>
- * Returns an empty list if there are no recently executed rollbacks.
+ * Returns an empty list if there are no recently committed rollbacks.
* <p>
* To avoid having to keep around complete rollback history forever on a
* device, the returned list of rollbacks is only guaranteed to include
@@ -107,12 +77,12 @@ public final class RollbackManager {
* (without the possibility of rollback) to a higher version code than was
* rolled back from.
*
- * @return the recently executed rollbacks
+ * @return the recently committed rollbacks
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() {
+ public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
try {
return mBinder.getRecentlyExecutedRollbacks().getList();
} catch (RemoteException e) {
@@ -121,25 +91,24 @@ public final class RollbackManager {
}
/**
- * Execute the given rollback, rolling back all versions of the packages
- * to the last good versions previously installed on the device as
- * specified in the given rollback object. The rollback will fail if any
- * of the installed packages or available rollbacks are inconsistent with
- * the versions specified in the given rollback object, which can happen
- * if a package has been updated or a rollback expired since the rollback
- * object was retrieved from {@link #getAvailableRollback(String)}.
+ * Commit the rollback with given id, rolling back all versions of the
+ * packages to the last good versions previously installed on the device
+ * as specified in the corresponding RollbackInfo object. The
+ * rollback will fail if any of the installed packages or available
+ * rollbacks are inconsistent with the versions specified in the given
+ * rollback object, which can happen if a package has been updated or a
+ * rollback expired since the rollback object was retrieved from
+ * {@link #getAvailableRollbacks()}.
* <p>
* TODO: Specify the returns status codes.
- * TODO: What happens in case reboot is required for the rollback to take
- * effect for staged installs?
*
- * @param rollback to execute
+ * @param rollback to commit
* @param statusReceiver where to deliver the results
* @throws SecurityException if the caller does not have the
* MANAGE_ROLLBACKS permission.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
- public void executeRollback(@NonNull RollbackInfo rollback,
+ public void commitRollback(@NonNull RollbackInfo rollback,
@NonNull IntentSender statusReceiver) {
try {
mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver);
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 8bcaa4526fa7..b44458a7d449 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -16,17 +16,20 @@
package android.database;
+import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.UserHandle;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
@@ -72,6 +75,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
@UnsupportedAppUsage
private Uri mNotifyUri;
+ private List<Uri> mNotifyUris;
private final Object mSelfObserverLock = new Object();
private ContentObserver mSelfObserver;
@@ -155,7 +159,11 @@ public abstract class AbstractCursor implements CrossProcessCursor {
@Override
public boolean requery() {
if (mSelfObserver != null && mSelfObserverRegistered == false) {
- mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+ final int size = mNotifyUris.size();
+ for (int i = 0; i < size; ++i) {
+ final Uri notifyUri = mNotifyUris.get(i);
+ mContentResolver.registerContentObserver(notifyUri, true, mSelfObserver);
+ }
mSelfObserverRegistered = true;
}
mDataSetObservable.notifyChanged();
@@ -384,8 +392,12 @@ public abstract class AbstractCursor implements CrossProcessCursor {
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
mContentObservable.dispatchChange(selfChange, null);
- if (mNotifyUri != null && selfChange) {
- mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
+ if (mNotifyUris != null && selfChange) {
+ final int size = mNotifyUris.size();
+ for (int i = 0; i < size; ++i) {
+ final Uri notifyUri = mNotifyUris.get(i);
+ mContentResolver.notifyChange(notifyUri, mSelfObserver);
+ }
}
}
}
@@ -399,19 +411,33 @@ public abstract class AbstractCursor implements CrossProcessCursor {
*/
@Override
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
- setNotificationUri(cr, notifyUri, cr.getUserId());
+ setNotificationUris(cr, Arrays.asList(notifyUri));
+ }
+
+ @Override
+ public void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> notifyUris) {
+ Preconditions.checkNotNull(cr);
+ Preconditions.checkNotNull(notifyUris);
+
+ setNotificationUris(cr, notifyUris, cr.getUserId());
}
/** @hide - set the notification uri but with an observer for a particular user's view */
- public void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) {
+ public void setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle) {
synchronized (mSelfObserverLock) {
- mNotifyUri = notifyUri;
+ mNotifyUris = notifyUris;
+ mNotifyUri = mNotifyUris.get(0);
mContentResolver = cr;
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
mSelfObserver = new SelfContentObserver(this);
- mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle);
+ final int size = mNotifyUris.size();
+ for (int i = 0; i < size; ++i) {
+ final Uri notifyUri = mNotifyUris.get(i);
+ mContentResolver.registerContentObserver(
+ notifyUri, true, mSelfObserver, userHandle);
+ }
mSelfObserverRegistered = true;
}
}
@@ -424,6 +450,13 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
@Override
+ public List<Uri> getNotificationUris() {
+ synchronized (mSelfObserverLock) {
+ return mNotifyUris;
+ }
+ }
+
+ @Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 5a4280e997cd..1379138b37c0 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -16,11 +16,14 @@
package android.database;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
/**
* This interface provides random read-write access to the result set returned
@@ -421,7 +424,10 @@ public interface Cursor extends Closeable {
/**
* Register to watch a content URI for changes. This can be the URI of a specific data row (for
* example, "content://my_provider_type/23"), or a a generic URI for a content type.
- *
+ *
+ * <p>Calling this overrides any previous call to
+ * {@link #setNotificationUris(ContentResolver, List)}.
+ *
* @param cr The content resolver from the caller's context. The listener attached to
* this resolver will be notified.
* @param uri The content URI to watch.
@@ -429,6 +435,24 @@ public interface Cursor extends Closeable {
void setNotificationUri(ContentResolver cr, Uri uri);
/**
+ * Similar to {@link #setNotificationUri(ContentResolver, Uri)}, except this version allows
+ * to watch multiple content URIs for changes.
+ *
+ * <p>If this is not implemented, this is equivalent to calling
+ * {@link #setNotificationUri(ContentResolver, Uri)} with the first URI in {@code uris}.
+ *
+ * <p>Calling this overrides any previous call to
+ * {@link #setNotificationUri(ContentResolver, Uri)}.
+ *
+ * @param cr The content resolver from the caller's context. The listener attached to
+ * this resolver will be notified.
+ * @param uris The content URIs to watch.
+ */
+ default void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> uris) {
+ setNotificationUri(cr, uris.get(0));
+ }
+
+ /**
* Return the URI at which notifications of changes in this Cursor's data
* will be delivered, as previously set by {@link #setNotificationUri}.
* @return Returns a URI that can be used with
@@ -439,6 +463,22 @@ public interface Cursor extends Closeable {
Uri getNotificationUri();
/**
+ * Return the URIs at which notifications of changes in this Cursor's data
+ * will be delivered, as previously set by {@link #setNotificationUris}.
+ *
+ * <p>If this is not implemented, this is equivalent to calling {@link #getNotificationUri()}.
+ *
+ * @return Returns URIs that can be used with
+ * {@link ContentResolver#registerContentObserver(android.net.Uri, boolean, ContentObserver)
+ * ContentResolver.registerContentObserver} to find out about changes to this Cursor's
+ * data. May be null if no notification URI has been set.
+ */
+ default List<Uri> getNotificationUris() {
+ final Uri notifyUri = getNotificationUri();
+ return notifyUri == null ? null : Arrays.asList(notifyUri);
+ }
+
+ /**
* onMove() will only be called across processes if this method returns true.
* @return whether all cursor movement should result in a call to onMove().
*/
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 0d27dfb872f0..c9cafafe4041 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -21,6 +21,8 @@ import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
+import java.util.List;
+
/**
* Wrapper class for Cursor that delegates all calls to the actual cursor object. The primary
* use for this class is to extend a cursor while overriding only a subset of its methods.
@@ -241,11 +243,21 @@ public class CursorWrapper implements Cursor {
}
@Override
+ public void setNotificationUris(ContentResolver cr, List<Uri> uris) {
+ mCursor.setNotificationUris(cr, uris);
+ }
+
+ @Override
public Uri getNotificationUri() {
return mCursor.getNotificationUri();
}
@Override
+ public List<Uri> getNotificationUris() {
+ return mCursor.getNotificationUris();
+ }
+
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
mCursor.unregisterContentObserver(observer);
}
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 9d37d9939127..209afb88ccd3 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -252,12 +252,55 @@ public interface BiometricFaceConstants {
public static final int FACE_ACQUIRED_TOO_SIMILAR = 15;
/**
+ * The magnitude of the pan angle of the user’s face with respect to the sensor’s
+ * capture plane is too high.
+ *
+ * The pan angle is defined as the angle swept out by the user’s face turning
+ * their neck left and right. The pan angle would be zero if the user faced the
+ * camera directly.
+ *
+ * The user should be informed to look more directly at the camera.
+ */
+ public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16;
+
+ /**
+ * The magnitude of the tilt angle of the user’s face with respect to the sensor’s
+ * capture plane is too high.
+ *
+ * The tilt angle is defined as the angle swept out by the user’s face looking up
+ * and down. The pan angle would be zero if the user faced the camera directly.
+ *
+ * The user should be informed to look more directly at the camera.
+ */
+ public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17;
+
+ /**
+ * The magnitude of the roll angle of the user’s face with respect to the sensor’s
+ * capture plane is too high.
+ *
+ * The roll angle is defined as the angle swept out by the user’s face tilting their head
+ * towards their shoulders to the left and right. The pan angle would be zero if the user
+ * faced the camera directly.
+ *
+ * The user should be informed to look more directly at the camera.
+ */
+ public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18;
+
+ /**
+ * The user’s face has been obscured by some object.
+ *
+ * The user should be informed to remove any objects from the line of sight from
+ * the sensor to the user’s face.
+ */
+ public static final int FACE_ACQUIRED_FACE_OBSCURED = 19;
+
+ /**
* Hardware vendors may extend this list if there are conditions that do not fall under one of
* the above categories. Vendors are responsible for providing error strings for these errors.
*
* @hide
*/
- public static final int FACE_ACQUIRED_VENDOR = 16;
+ public static final int FACE_ACQUIRED_VENDOR = 20;
/**
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 8aac1bfa1f8d..5afe1a9309f4 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -171,5 +171,39 @@ public class BiometricManager {
Slog.w(TAG, "resetTimeout(): Service not connected");
}
}
+
+ /**
+ * TODO(b/123378871): Remove when moved.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onConfirmDeviceCredentialSuccess() {
+ if (mService != null) {
+ try {
+ mService.onConfirmDeviceCredentialSuccess();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected");
+ }
+ }
+
+ /**
+ * TODO(b/123378871): Remove when moved.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onConfirmDeviceCredentialError(int error, String message) {
+ if (mService != null) {
+ try {
+ mService.onConfirmDeviceCredentialError(error, message);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected");
+ }
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c69b68e4360a..ec62abaab6a2 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -77,6 +77,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
+ /**
+ * @hide
+ */
+ public static final String KEY_ENABLE_FALLBACK = "enable_fallback";
/**
* Error/help message will show for this amount of time.
@@ -242,6 +246,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * The user will first be prompted to authenticate with biometrics, but also given the
+ * option to authenticate with their device PIN, pattern, or password.
+ * @param enable When true, the prompt will fall back to ask for the user's device
+ * credentials (PIN, pattern, or password).
+ * @return
+ */
+ public Builder setEnableFallback(boolean enable) {
+ mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable);
+ return this;
+ }
+
+ /**
* Creates a {@link BiometricPrompt}.
* @return a {@link BiometricPrompt}
* @throws IllegalArgumentException if any of the required fields are not set.
@@ -250,11 +266,15 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
+ final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK);
if (TextUtils.isEmpty(title) && !useDefaultTitle) {
throw new IllegalArgumentException("Title must be set and non-empty");
- } else if (TextUtils.isEmpty(negative)) {
+ } else if (TextUtils.isEmpty(negative) && !enableFallback) {
throw new IllegalArgumentException("Negative text must be set and non-empty");
+ } else if (!TextUtils.isEmpty(negative) && enableFallback) {
+ throw new IllegalArgumentException("Can't have both negative button behavior"
+ + " and fallback enabled");
}
return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
}
@@ -514,6 +534,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
+ if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) {
+ throw new IllegalArgumentException("Fallback not supported with crypto");
+ }
authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index de828f2d6757..e4336d171ab6 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -51,4 +51,12 @@ interface IBiometricService {
// Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
void resetTimeout(in byte [] token);
+
+ // TODO(b/123378871): Remove when moved.
+ // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's
+ // setEnableFallback method, since there's no way for us to intercept onActivityResult.
+ // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult.
+ void onConfirmDeviceCredentialSuccess();
+ // TODO(b/123378871): Remove when moved.
+ void onConfirmDeviceCredentialError(int error, String message);
}
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index fa335c8b9924..27f0b0425ec3 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -23,16 +23,22 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.content.ContentResolver;
import android.content.Context;
+import android.metrics.LogMaker;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.provider.Settings.Secure;
import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.LocalTime;
/**
* Manages the display's color transforms and modes.
@@ -81,7 +87,82 @@ public final class ColorDisplayManager {
@SystemApi
public static final int CAPABILITY_HARDWARE_ACCELERATION_PER_APP = 0x4;
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM_TIME, AUTO_MODE_TWILIGHT })
+ public @interface AutoMode {}
+
+ /**
+ * Auto mode value to prevent Night display from being automatically activated. It can still
+ * be activated manually via {@link #setNightDisplayActivated(boolean)}.
+ *
+ * @see #setNightDisplayAutoMode(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int AUTO_MODE_DISABLED = 0;
+ /**
+ * Auto mode value to automatically activate Night display at a specific start and end time.
+ *
+ * @see #setNightDisplayAutoMode(int)
+ * @see #setNightDisplayCustomStartTime(LocalTime)
+ * @see #setNightDisplayCustomEndTime(LocalTime)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int AUTO_MODE_CUSTOM_TIME = 1;
+ /**
+ * Auto mode value to automatically activate Night display from sunset to sunrise.
+ *
+ * @see #setNightDisplayAutoMode(int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int AUTO_MODE_TWILIGHT = 2;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC})
+ public @interface ColorMode {}
+
+ /**
+ * Color mode with natural colors.
+ *
+ * @hide
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_NATURAL = 0;
+ /**
+ * Color mode with boosted colors.
+ *
+ * @hide
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_BOOSTED = 1;
+ /**
+ * Color mode with saturated colors.
+ *
+ * @hide
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_SATURATED = 2;
+ /**
+ * Color mode with automatic colors.
+ *
+ * @hide
+ * @see #setColorMode(int)
+ */
+ public static final int COLOR_MODE_AUTOMATIC = 3;
+
private final ColorDisplayManagerInternal mManager;
+ private MetricsLogger mMetricsLogger;
/**
* @hide
@@ -91,6 +172,176 @@ public final class ColorDisplayManager {
}
/**
+ * (De)activates the night display transform.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setNightDisplayActivated(boolean activated) {
+ return mManager.setNightDisplayActivated(activated);
+ }
+
+ /**
+ * Returns whether the night display transform is currently active.
+ *
+ * @hide
+ */
+ public boolean isNightDisplayActivated() {
+ return mManager.isNightDisplayActivated();
+ }
+
+ /**
+ * Sets the color temperature of the night display transform.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setNightDisplayColorTemperature(int temperature) {
+ return mManager.setNightDisplayColorTemperature(temperature);
+ }
+
+ /**
+ * Gets the color temperature of the night display transform.
+ *
+ * @hide
+ */
+ public int getNightDisplayColorTemperature() {
+ return mManager.getNightDisplayColorTemperature();
+ }
+
+ /**
+ * Returns the current auto mode value controlling when Night display will be automatically
+ * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or
+ * {@link #AUTO_MODE_TWILIGHT}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public @AutoMode int getNightDisplayAutoMode() {
+ return mManager.getNightDisplayAutoMode();
+ }
+
+ /**
+ * Returns the current auto mode value, without validation, or {@code 1} if the auto mode has
+ * never been set.
+ *
+ * @hide
+ */
+ public int getNightDisplayAutoModeRaw() {
+ return mManager.getNightDisplayAutoModeRaw();
+ }
+
+ /**
+ * Sets the current auto mode value controlling when Night display will be automatically
+ * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM_TIME}, or
+ * {@link #AUTO_MODE_TWILIGHT}.
+ *
+ * @param autoMode the new auto mode to use
+ * @return {@code true} if new auto mode was set successfully
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setNightDisplayAutoMode(@AutoMode int autoMode) {
+ if (autoMode != AUTO_MODE_DISABLED
+ && autoMode != AUTO_MODE_CUSTOM_TIME
+ && autoMode != AUTO_MODE_TWILIGHT) {
+ throw new IllegalArgumentException("Invalid autoMode: " + autoMode);
+ }
+ if (mManager.getNightDisplayAutoMode() != autoMode) {
+ getMetricsLogger().write(new LogMaker(
+ MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(autoMode));
+ }
+ return mManager.setNightDisplayAutoMode(autoMode);
+ }
+
+ /**
+ * Returns the local time when Night display will be automatically activated when using
+ * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
+ *
+ * @hide
+ */
+ public @NonNull LocalTime getNightDisplayCustomStartTime() {
+ return mManager.getNightDisplayCustomStartTime().getLocalTime();
+ }
+
+ /**
+ * Sets the local time when Night display will be automatically activated when using
+ * {@link ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
+ *
+ * @param startTime the local time to automatically activate Night display
+ * @return {@code true} if the new custom start time was set successfully
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setNightDisplayCustomStartTime(@NonNull LocalTime startTime) {
+ if (startTime == null) {
+ throw new IllegalArgumentException("startTime cannot be null");
+ }
+ getMetricsLogger().write(new LogMaker(
+ MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(0));
+ return mManager.setNightDisplayCustomStartTime(new Time(startTime));
+ }
+
+ /**
+ * Returns the local time when Night display will be automatically deactivated when using
+ * {@link #AUTO_MODE_CUSTOM_TIME}.
+ *
+ * @hide
+ */
+ public @NonNull LocalTime getNightDisplayCustomEndTime() {
+ return mManager.getNightDisplayCustomEndTime().getLocalTime();
+ }
+
+ /**
+ * Sets the local time when Night display will be automatically deactivated when using
+ * {@link #AUTO_MODE_CUSTOM_TIME}.
+ *
+ * @param endTime the local time to automatically deactivate Night display
+ * @return {@code true} if the new custom end time was set successfully
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setNightDisplayCustomEndTime(@NonNull LocalTime endTime) {
+ if (endTime == null) {
+ throw new IllegalArgumentException("endTime cannot be null");
+ }
+ getMetricsLogger().write(new LogMaker(
+ MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(1));
+ return mManager.setNightDisplayCustomEndTime(new Time(endTime));
+ }
+
+ /**
+ * Sets the current display color mode.
+ *
+ * @hide
+ */
+ public void setColorMode(int colorMode) {
+ mManager.setColorMode(colorMode);
+ }
+
+ /**
+ * Gets the current display color mode.
+ *
+ * @hide
+ */
+ public int getColorMode() {
+ return mManager.getColorMode();
+ }
+
+ /**
* Returns whether the device has a wide color gamut display.
*
* @hide
@@ -138,6 +389,28 @@ public final class ColorDisplayManager {
}
/**
+ * Returns the minimum allowed color temperature (in Kelvin) to tint the display when
+ * activated.
+ *
+ * @hide
+ */
+ public static int getMinimumColorTemperature(Context context) {
+ return context.getResources()
+ .getInteger(R.integer.config_nightDisplayColorTemperatureMin);
+ }
+
+ /**
+ * Returns the maximum allowed color temperature (in Kelvin) to tint the display when
+ * activated.
+ *
+ * @hide
+ */
+ public static int getMaximumColorTemperature(Context context) {
+ return context.getResources()
+ .getInteger(R.integer.config_nightDisplayColorTemperatureMax);
+ }
+
+ /**
* Returns {@code true} if display white balance is supported by the device.
*
* @hide
@@ -167,6 +440,25 @@ public final class ColorDisplayManager {
return mManager.getTransformCapabilities();
}
+ /**
+ * Returns whether accessibility transforms are currently enabled, which determines whether
+ * color modes are currently configurable for this device.
+ *
+ * @hide
+ */
+ public static boolean areAccessibilityTransformsEnabled(Context context) {
+ final ContentResolver cr = context.getContentResolver();
+ return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1
+ || Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0) == 1;
+ }
+
+ private MetricsLogger getMetricsLogger() {
+ if (mMetricsLogger == null) {
+ mMetricsLogger = new MetricsLogger();
+ }
+ return mMetricsLogger;
+ }
+
private static class ColorDisplayManagerInternal {
private static ColorDisplayManagerInternal sInstance;
@@ -192,6 +484,94 @@ public final class ColorDisplayManager {
}
}
+ boolean isNightDisplayActivated() {
+ try {
+ return mCdm.isNightDisplayActivated();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setNightDisplayActivated(boolean activated) {
+ try {
+ return mCdm.setNightDisplayActivated(activated);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ int getNightDisplayColorTemperature() {
+ try {
+ return mCdm.getNightDisplayColorTemperature();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setNightDisplayColorTemperature(int temperature) {
+ try {
+ return mCdm.setNightDisplayColorTemperature(temperature);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ int getNightDisplayAutoMode() {
+ try {
+ return mCdm.getNightDisplayAutoMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ int getNightDisplayAutoModeRaw() {
+ try {
+ return mCdm.getNightDisplayAutoModeRaw();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setNightDisplayAutoMode(int autoMode) {
+ try {
+ return mCdm.setNightDisplayAutoMode(autoMode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ Time getNightDisplayCustomStartTime() {
+ try {
+ return mCdm.getNightDisplayCustomStartTime();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setNightDisplayCustomStartTime(Time startTime) {
+ try {
+ return mCdm.setNightDisplayCustomStartTime(startTime);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ Time getNightDisplayCustomEndTime() {
+ try {
+ return mCdm.getNightDisplayCustomEndTime();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ boolean setNightDisplayCustomEndTime(Time endTime) {
+ try {
+ return mCdm.setNightDisplayCustomEndTime(endTime);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
boolean isDeviceColorManaged() {
try {
return mCdm.isDeviceColorManaged();
@@ -216,6 +596,22 @@ public final class ColorDisplayManager {
}
}
+ int getColorMode() {
+ try {
+ return mCdm.getColorMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ void setColorMode(int colorMode) {
+ try {
+ mCdm.setColorMode(colorMode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
int getTransformCapabilities() {
try {
return mCdm.getTransformCapabilities();
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7e45441c804a..f3ebd7f36fd6 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,6 +20,7 @@ import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
+import android.graphics.ColorSpace;
import android.graphics.Point;
import android.hardware.display.DisplayManager.DisplayListener;
import android.media.projection.IMediaProjection;
@@ -80,12 +81,20 @@ public final class DisplayManagerGlobal {
new ArrayList<DisplayListenerDelegate>();
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
+ private final ColorSpace mWideColorSpace;
private int[] mDisplayIdCache;
private int mWifiDisplayScanNestCount;
private DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
+ try {
+ mWideColorSpace =
+ ColorSpace.get(
+ ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
}
/**
@@ -495,6 +504,17 @@ public final class DisplayManagerGlobal {
}
/**
+ * Gets the preferred wide gamut color space for all displays.
+ * The wide gamut color space is returned from composition pipeline
+ * based on hardware capability.
+ *
+ * @hide
+ */
+ public ColorSpace getPreferredWideGamutColorSpace() {
+ return mWideColorSpace;
+ }
+
+ /**
* Sets the global brightness configuration for a given user.
*
* @hide
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 53cb8db8cc3d..30e76cfe2787 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -16,6 +16,8 @@
package android.hardware.display;
+import android.hardware.display.Time;
+
/** @hide */
interface IColorDisplayManager {
boolean isDeviceColorManaged();
@@ -24,4 +26,19 @@ interface IColorDisplayManager {
boolean setAppSaturationLevel(String packageName, int saturationLevel);
int getTransformCapabilities();
+
+ boolean isNightDisplayActivated();
+ boolean setNightDisplayActivated(boolean activated);
+ int getNightDisplayColorTemperature();
+ boolean setNightDisplayColorTemperature(int temperature);
+ int getNightDisplayAutoMode();
+ int getNightDisplayAutoModeRaw();
+ boolean setNightDisplayAutoMode(int autoMode);
+ Time getNightDisplayCustomStartTime();
+ boolean setNightDisplayCustomStartTime(in Time time);
+ Time getNightDisplayCustomEndTime();
+ boolean setNightDisplayCustomEndTime(in Time time);
+
+ int getColorMode();
+ void setColorMode(int colorMode);
} \ No newline at end of file
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index aae8afbcad49..5ea8bd67ce75 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -116,4 +116,9 @@ interface IDisplayManager {
// Get the minimum brightness curve.
Curve getMinimumBrightnessCurve();
+
+ // Gets the id of the preferred wide gamut color space for all displays.
+ // The wide gamut color space is returned from composition pipeline
+ // based on hardware capability.
+ int getPreferredWideGamutColorSpaceId();
}
diff --git a/core/java/android/hardware/display/Time.aidl b/core/java/android/hardware/display/Time.aidl
new file mode 100644
index 000000000000..95cb5631e72f
--- /dev/null
+++ b/core/java/android/hardware/display/Time.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.display;
+
+parcelable Time; \ No newline at end of file
diff --git a/core/java/android/hardware/display/Time.java b/core/java/android/hardware/display/Time.java
new file mode 100644
index 000000000000..b943ac65ab06
--- /dev/null
+++ b/core/java/android/hardware/display/Time.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 android.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.LocalTime;
+
+/**
+ * @hide
+ */
+public final class Time implements Parcelable {
+
+ private final int mHour;
+ private final int mMinute;
+ private final int mSecond;
+ private final int mNano;
+
+ public Time(LocalTime localTime) {
+ mHour = localTime.getHour();
+ mMinute = localTime.getMinute();
+ mSecond = localTime.getSecond();
+ mNano = localTime.getNano();
+ }
+
+ public Time(Parcel parcel) {
+ mHour = parcel.readInt();
+ mMinute = parcel.readInt();
+ mSecond = parcel.readInt();
+ mNano = parcel.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mHour);
+ parcel.writeInt(mMinute);
+ parcel.writeInt(mSecond);
+ parcel.writeInt(mNano);
+ }
+
+ public LocalTime getLocalTime() {
+ return LocalTime.of(mHour, mMinute, mSecond, mNano);
+ }
+
+ public static final Parcelable.Creator<Time> CREATOR = new Parcelable.Creator<Time>() {
+
+ @Override
+ public Time createFromParcel(Parcel source) {
+ return new Time(source);
+ }
+
+ @Override
+ public Time[] newArray(int size) {
+ return new Time[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index a98b31ad6a5e..56020b24c556 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -18,6 +18,7 @@ package android.hardware.hdmi;
import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
@@ -33,6 +34,8 @@ import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.List;
/**
@@ -106,9 +109,24 @@ public final class HdmiControlManager {
public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
+ @IntDef ({
+ RESULT_SUCCESS,
+ RESULT_TIMEOUT,
+ RESULT_SOURCE_NOT_AVAILABLE,
+ RESULT_TARGET_NOT_AVAILABLE,
+ RESULT_ALREADY_IN_PROGRESS,
+ RESULT_EXCEPTION,
+ RESULT_INCORRECT_MODE,
+ RESULT_COMMUNICATION_FAILED,
+ })
+ public @interface ControlCallbackResult {}
+
+ /** Control operation is successfully handled by the framework. */
public static final int RESULT_SUCCESS = 0;
public static final int RESULT_TIMEOUT = 1;
+ /** Source device that the application is using is not available. */
public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
+ /** Target device that the application is controlling is not available. */
public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
@Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
@@ -394,16 +412,15 @@ public final class HdmiControlManager {
/**
* Gets an object that represents an HDMI-CEC logical device of type switch on the system.
*
- * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
- * possible to communicate with other logical devices hosted in the same system if the system is
- * configured to host more than one type of HDMI-CEC logical devices.
+ * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus.
+ * It is also possible to communicate with other logical devices hosted in the same
+ * system if the system is configured to host more than one type of HDMI-CEC logical device.
*
* @return {@link HdmiSwitchClient} instance. {@code null} on failure.
- *
- * TODO(b/110094868): unhide for Q
* @hide
*/
@Nullable
+ @SystemApi
@SuppressLint("Doclava125")
public HdmiSwitchClient getSwitchClient() {
return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
@@ -412,11 +429,15 @@ public final class HdmiControlManager {
/**
* Get a snapshot of the real-time status of the remote devices.
*
- * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device.
+ * <p>This only applies to devices with multiple HDMI inputs.
+ *
+ * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty
+ * list will be returned if there is none.
*
- * TODO(b/110094868): unhide for Q
* @hide
*/
+ @SystemApi
+ @Nullable
public List<HdmiDeviceInfo> getConnectedDevicesList() {
try {
return mService.getDeviceList();
@@ -426,14 +447,17 @@ public final class HdmiControlManager {
}
/**
- * Power off the target device.
+ * Power off the target device by sending CEC commands.
*
- * @param deviceInfo HdmiDeviceInfo of the device to be powered off
+ * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
+ *
+ * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
*
- * TODO(b/110094868): unhide for Q
* @hide
*/
+ @SystemApi
public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) {
+ Preconditions.checkNotNull(deviceInfo);
try {
mService.powerOffRemoteDevice(
deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -443,14 +467,16 @@ public final class HdmiControlManager {
}
/**
- * Power on the target device.
+ * Power on the target device by sending CEC commands.
*
- * @param deviceInfo HdmiDeviceInfo of the device to be powered on
+ * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
+ *
+ * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on.
*
- * TODO(b/110094868): unhide for Q
* @hide
*/
public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
+ Preconditions.checkNotNull(deviceInfo);
try {
mService.powerOnRemoteDevice(
deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -460,14 +486,17 @@ public final class HdmiControlManager {
}
/**
- * Ask the target device to be the new Active Source.
+ * Request the target device to be the new Active Source by sending CEC commands.
+ *
+ * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
*
* @param deviceInfo HdmiDeviceInfo of the target device
*
- * TODO(b/110094868): unhide for Q
* @hide
*/
- public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+ @SystemApi
+ public void requestRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+ Preconditions.checkNotNull(deviceInfo);
try {
mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
} catch (RemoteException e) {
@@ -506,8 +535,13 @@ public final class HdmiControlManager {
/**
* Get the physical address of the device.
*
+ * <p>Physical address needs to be automatically adjusted when devices are phyiscally or
+ * electrically added or removed from the device tree. Please see HDMI Specification Version
+ * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
+ *
* @hide
*/
+ @SystemApi
public int getPhysicalAddress() {
if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) {
return mPhysicalAddress;
@@ -521,16 +555,19 @@ public final class HdmiControlManager {
}
/**
- * Check if the target device is connected to the current device. The
- * API also returns true if the current device is the target.
+ * Check if the target remote device is connected to the current device.
+ *
+ * <p>The API also returns true if the current device is the target.
*
* @param targetDevice {@link HdmiDeviceInfo} of the target device.
- * @return true if {@code device} is directly or indirectly connected to the
+ * @return true if {@code targetDevice} is directly or indirectly
+ * connected to the current device.
*
- * TODO(b/110094868): unhide for Q
* @hide
*/
- public boolean isTargetDeviceConnected(HdmiDeviceInfo targetDevice) {
+ @SystemApi
+ public boolean isRemoteDeviceConnected(HdmiDeviceInfo targetDevice) {
+ Preconditions.checkNotNull(targetDevice);
mPhysicalAddress = getPhysicalAddress();
if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
return false;
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 1ac29736f964..a0365129a89a 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -15,24 +15,30 @@
*/
package android.hardware.hdmi;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult;
+import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
/**
- * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which
- * acts as switch.
+ * An {@code HdmiSwitchClient} represents a HDMI-CEC switch device.
*
- * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH,
- * but it is used by all Android TV devices that have multiple HDMI inputs,
- * even if it is not a "pure" swicth and has another device type like TV or Player.
+ * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, but it is
+ * used by all Android devices that have multiple HDMI inputs, even if it is not a "pure" swicth
+ * and has another device type like TV or Player.
*
* @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
*/
+@SystemApi
public class HdmiSwitchClient extends HdmiClient {
private static final String TAG = "HdmiSwitchClient";
@@ -41,17 +47,15 @@ public class HdmiSwitchClient extends HdmiClient {
super(service);
}
- private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+ private static IHdmiControlCallback getCallbackWrapper(final OnSelectListener listener) {
return new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
- callback.onComplete(result);
+ listener.onSelect(result);
}
};
}
- /** @hide */
- // TODO(b/110094868): unhide for Q
@Override
public int getDeviceType() {
return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
@@ -61,20 +65,17 @@ public class HdmiSwitchClient extends HdmiClient {
* Selects a CEC logical device to be a new active source.
*
* @param logicalAddress logical address of the device to select
- * @param callback callback to get the result with
- * @throws {@link IllegalArgumentException} if the {@code callback} is null
+ * @param listener listener to get the result with
*
* @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
*/
- public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null.");
- }
+ public void selectDevice(int logicalAddress, @NonNull OnSelectListener listener) {
+ Preconditions.checkNotNull(listener);
try {
- mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+ mService.deviceSelect(logicalAddress, getCallbackWrapper(listener));
} catch (RemoteException e) {
Log.e(TAG, "failed to select device: ", e);
+ throw e.rethrowFromSystemServer();
}
}
@@ -82,20 +83,83 @@ public class HdmiSwitchClient extends HdmiClient {
* Selects a HDMI port to be a new route path.
*
* @param portId HDMI port to select
- * @param callback callback to get the result with
- * @throws {@link IllegalArgumentException} if the {@code callback} is null
+ * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()}
+ * to get portId of a specific TV Input.
+ * @param listener listener to get the result with
*
* @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
*/
- public void portSelect(int portId, @NonNull SelectCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("Callback must not be null");
+ @SystemApi
+ public void selectPort(int portId, @NonNull OnSelectListener listener) {
+ Preconditions.checkNotNull(listener);
+ try {
+ mService.portSelect(portId, getCallbackWrapper(listener));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to select port: ", e);
+ throw e.rethrowFromSystemServer();
}
+ }
+
+ /**
+ * Selects a CEC logical device to be a new active source.
+ *
+ * @param logicalAddress logical address of the device to select
+ * @param executor executor to allow the develper to specify the thread upon which the listeners
+ * will be invoked
+ * @param listener listener to get the result with
+ *
+ * @hide
+ */
+ public void selectDevice(
+ int logicalAddress,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSelectListener listener) {
+ Preconditions.checkNotNull(listener);
+ try {
+ mService.deviceSelect(logicalAddress,
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ Binder.withCleanCallingIdentity(
+ () -> executor.execute(() -> listener.onSelect(result)));
+ }
+ }
+ );
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to select device: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Selects a HDMI port to be a new route path.
+ *
+ * @param portId HDMI port to select
+ * @param executor executor to allow the develper to specify the thread upon which the listeners
+ * will be invoked
+ * @param listener listener to get the result with
+ *
+ * @hide
+ */
+ @SystemApi
+ public void selectPort(
+ int portId,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSelectListener listener) {
+ Preconditions.checkNotNull(listener);
try {
- mService.portSelect(portId, getCallbackWrapper(callback));
+ mService.portSelect(portId,
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ Binder.withCleanCallingIdentity(
+ () -> executor.execute(() -> listener.onSelect(result)));
+ }
+ }
+ );
} catch (RemoteException e) {
Log.e(TAG, "failed to select port: ", e);
+ throw e.rethrowFromSystemServer();
}
}
@@ -105,10 +169,9 @@ public class HdmiSwitchClient extends HdmiClient {
* <p>This only applies to device with multiple HDMI inputs
*
* @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if
- * there is none.
+ * there is none.
*
* @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
*/
public List<HdmiDeviceInfo> getDeviceList() {
try {
@@ -120,18 +183,19 @@ public class HdmiSwitchClient extends HdmiClient {
}
/**
- * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
+ * Listener interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
*
* @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
*/
- public interface SelectCallback {
+ @SystemApi
+ public interface OnSelectListener {
/**
* Called when the operation is finished.
*
- * @param result the result value of {@link #deviceSelect} or {@link #portSelect}.
+ * @param result callback result.
+ * @see {@link ControlCallbackResult}
*/
- void onComplete(int result);
+ void onSelect(@ControlCallbackResult int result);
}
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 103069474445..37b25c8fec0c 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -179,19 +179,24 @@ class IInputMethodWrapper extends IInputMethod.Stub
return;
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
- final int missingMethods = msg.arg1;
- final boolean restarting = msg.arg2 != 0;
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+ SomeArgs moreArgs = (SomeArgs) args.arg5;
final InputConnection ic = inputContext != null
? new InputConnectionWrapper(
- mTarget, inputContext, missingMethods, isUnbindIssued) : null;
+ mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+ : null;
info.makeCompatible(mTargetSdkVersion);
- inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
- startInputToken);
+ inputMethod.dispatchStartInputWithToken(
+ ic,
+ info,
+ moreArgs.argi1 == 1 /* restarting */,
+ startInputToken,
+ moreArgs.argi2 == 1 /* shouldPreRenderIme */);
args.recycle();
+ moreArgs.recycle();
return;
}
case DO_CREATE_SESSION: {
@@ -291,14 +296,17 @@ class IInputMethodWrapper extends IInputMethod.Stub
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
- EditorInfo attribute, boolean restarting) {
+ EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
if (mIsUnbindIssued == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mIsUnbindIssued = new AtomicBoolean();
}
- mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
- missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
- mIsUnbindIssued));
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = restarting ? 1 : 0;
+ args.argi2 = shouldPreRenderIme ? 1 : 0;
+ args.argi3 = missingMethods;
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
+ DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 38ddc169f527..5b3ad77dbf43 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -346,6 +346,13 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public static final int IME_VISIBLE = 0x2;
+ /**
+ * @hide
+ * The IME is active and ready with views but set invisible.
+ * This flag cannot be combined with {@link #IME_VISIBLE}.
+ */
+ public static final int IME_INVISIBLE = 0x4;
+
// Min and max values for back disposition.
private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;
@@ -362,10 +369,19 @@ public class InputMethodService extends AbstractInputMethodService {
View mRootView;
SoftInputWindow mWindow;
boolean mInitialized;
- boolean mWindowCreated;
- boolean mWindowVisible;
- boolean mWindowWasVisible;
+ boolean mViewsCreated;
+ // IME views visibility.
+ boolean mDecorViewVisible;
+ boolean mDecorViewWasVisible;
boolean mInShowWindow;
+ // True if pre-rendering of IME views/window is supported.
+ boolean mCanPreRender;
+ // If IME is pre-rendered.
+ boolean mIsPreRendered;
+ // IME window visibility.
+ // Use (mDecorViewVisible && mWindowVisible) to check if IME is visible to the user.
+ boolean mWindowVisible;
+
ViewGroup mFullscreenArea;
FrameLayout mExtractFrame;
FrameLayout mCandidatesFrame;
@@ -552,15 +568,18 @@ public class InputMethodService extends AbstractInputMethodService {
*/
@MainThread
@Override
- public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
+ public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken) {
+ @NonNull IBinder startInputToken, boolean shouldPreRenderIme) {
mPrivOps.reportStartInput(startInputToken);
- // This needs to be dispatched to interface methods rather than doStartInput().
- // Otherwise IME developers who have overridden those interface methods will lose
- // notifications.
- super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting,
- startInputToken);
+ mCanPreRender = shouldPreRenderIme;
+ if (DEBUG) Log.v(TAG, "Will Pre-render IME: " + mCanPreRender);
+
+ if (restarting) {
+ restartInput(inputConnection, editorInfo);
+ } else {
+ startInput(inputConnection, editorInfo);
+ }
}
/**
@@ -570,14 +589,27 @@ public class InputMethodService extends AbstractInputMethodService {
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
- boolean wasVis = isInputViewShown();
- mShowInputFlags = 0;
- mShowInputRequested = false;
- doHideWindow();
+ final boolean wasVisible = mIsPreRendered
+ ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+ if (mIsPreRendered) {
+ // TODO: notify visibility to insets consumer.
+ if (DEBUG) {
+ Log.v(TAG, "Making IME window invisible");
+ }
+ setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
+ onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+ } else {
+ mShowInputFlags = 0;
+ mShowInputRequested = false;
+ doHideWindow();
+ }
+ final boolean isVisible = mIsPreRendered
+ ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+ final boolean visibilityChanged = isVisible != wasVisible;
if (resultReceiver != null) {
- resultReceiver.send(wasVis != isInputViewShown()
+ resultReceiver.send(visibilityChanged
? InputMethodManager.RESULT_HIDDEN
- : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
}
}
@@ -589,17 +621,28 @@ public class InputMethodService extends AbstractInputMethodService {
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
- boolean wasVis = isInputViewShown();
+ final boolean wasVisible = mIsPreRendered
+ ? mDecorViewVisible && mWindowVisible : isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
- showWindow(true);
+ if (mIsPreRendered) {
+ // TODO: notify visibility to insets consumer.
+ if (DEBUG) {
+ Log.v(TAG, "Making IME window visible");
+ }
+ onPreRenderedWindowVisibilityChanged(true /* setVisible */);
+ } else {
+ showWindow(true);
+ }
}
// If user uses hard keyboard, IME button should always be shown.
- setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
-
+ setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
+ final boolean isVisible = mIsPreRendered
+ ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+ final boolean visibilityChanged = isVisible != wasVisible;
if (resultReceiver != null) {
- resultReceiver.send(wasVis != isInputViewShown()
+ resultReceiver.send(visibilityChanged
? InputMethodManager.RESULT_SHOWN
- : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
}
}
@@ -972,7 +1015,7 @@ public class InputMethodService extends AbstractInputMethodService {
void initViews() {
mInitialized = false;
- mWindowCreated = false;
+ mViewsCreated = false;
mShowInputRequested = false;
mShowInputFlags = 0;
@@ -1046,7 +1089,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
private void resetStateForNewConfiguration() {
- boolean visible = mWindowVisible;
+ boolean visible = mDecorViewVisible;
int showFlags = mShowInputFlags;
boolean showingInput = mShowInputRequested;
CompletionInfo[] completions = mCurCompletions;
@@ -1129,7 +1172,7 @@ public class InputMethodService extends AbstractInputMethodService {
return;
}
mBackDisposition = disposition;
- setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
+ setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
}
/**
@@ -1380,7 +1423,7 @@ public class InputMethodService extends AbstractInputMethodService {
mExtractFrame.setVisibility(View.GONE);
}
updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
- if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) {
+ if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) {
int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE
? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation
: com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation,
@@ -1439,7 +1482,7 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public void updateInputViewShown() {
boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
- if (mIsInputViewShown != isShown && mWindowVisible) {
+ if (mIsInputViewShown != isShown && mDecorViewVisible) {
mIsInputViewShown = isShown;
mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
if (mInputView == null) {
@@ -1458,14 +1501,14 @@ public class InputMethodService extends AbstractInputMethodService {
public boolean isShowInputRequested() {
return mShowInputRequested;
}
-
+
/**
* Return whether the soft input view is <em>currently</em> shown to the
* user. This is the state that was last determined and
* applied by {@link #updateInputViewShown()}.
*/
public boolean isInputViewShown() {
- return mIsInputViewShown && mWindowVisible;
+ return mCanPreRender ? mWindowVisible : mIsInputViewShown && mDecorViewVisible;
}
/**
@@ -1499,7 +1542,7 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public void setCandidatesViewShown(boolean shown) {
updateCandidatesVisibility(shown);
- if (!mShowInputRequested && mWindowVisible != shown) {
+ if (!mShowInputRequested && mDecorViewVisible != shown) {
// If we are being asked to show the candidates view while the app
// has not asked for the input view to be shown, then we need
// to update whether the window is shown.
@@ -1804,7 +1847,8 @@ public class InputMethodService extends AbstractInputMethodService {
public void showWindow(boolean showInput) {
if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+ " mShowInputRequested=" + mShowInputRequested
- + " mWindowCreated=" + mWindowCreated
+ + " mViewsCreated=" + mViewsCreated
+ + " mDecorViewVisible=" + mDecorViewVisible
+ " mWindowVisible=" + mWindowVisible
+ " mInputStarted=" + mInputStarted
+ " mShowInputFlags=" + mShowInputFlags);
@@ -1814,18 +1858,42 @@ public class InputMethodService extends AbstractInputMethodService {
return;
}
- mWindowWasVisible = mWindowVisible;
+ mDecorViewWasVisible = mDecorViewVisible;
mInShowWindow = true;
- showWindowInner(showInput);
- mWindowWasVisible = true;
+ boolean isPreRenderedAndInvisible = mIsPreRendered && !mWindowVisible;
+ final int previousImeWindowStatus =
+ (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
+ ? (isPreRenderedAndInvisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
+ startViews(prepareWindow(showInput));
+ final int nextImeWindowStatus = mapToImeWindowStatus();
+ if (previousImeWindowStatus != nextImeWindowStatus) {
+ setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
+ }
+
+ // compute visibility
+ onWindowShown();
+ mIsPreRendered = mCanPreRender;
+ if (mIsPreRendered) {
+ onPreRenderedWindowVisibilityChanged(true /* setVisible */);
+ } else {
+ // Pre-rendering not supported.
+ if (DEBUG) Log.d(TAG, "No pre-rendering supported");
+ mWindowVisible = true;
+ }
+
+ // request draw for the IME surface.
+ // When IME is not pre-rendered, this will actually show the IME.
+ if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
+ if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
+ mWindow.show();
+ }
+ mDecorViewWasVisible = true;
mInShowWindow = false;
}
- void showWindowInner(boolean showInput) {
+ private boolean prepareWindow(boolean showInput) {
boolean doShowInput = false;
- final int previousImeWindowStatus =
- (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
- mWindowVisible = true;
+ mDecorViewVisible = true;
if (!mShowInputRequested && mInputStarted && showInput) {
doShowInput = true;
mShowInputRequested = true;
@@ -1836,8 +1904,8 @@ public class InputMethodService extends AbstractInputMethodService {
updateFullscreenMode();
updateInputViewShown();
- if (!mWindowCreated) {
- mWindowCreated = true;
+ if (!mViewsCreated) {
+ mViewsCreated = true;
initialize();
if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
View v = onCreateCandidatesView();
@@ -1846,6 +1914,10 @@ public class InputMethodService extends AbstractInputMethodService {
setCandidatesView(v);
}
}
+ return doShowInput;
+ }
+
+ private void startViews(boolean doShowInput) {
if (mShowInputRequested) {
if (!mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
@@ -1857,29 +1929,26 @@ public class InputMethodService extends AbstractInputMethodService {
mCandidatesViewStarted = true;
onStartCandidatesView(mInputEditorInfo, false);
}
-
- if (doShowInput) {
- startExtractingText(false);
- }
+ if (doShowInput) startExtractingText(false);
+ }
- final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
- if (previousImeWindowStatus != nextImeWindowStatus) {
- setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
- }
- if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
- if (DEBUG) Log.v(TAG, "showWindow: showing!");
+ private void onPreRenderedWindowVisibilityChanged(boolean setVisible) {
+ mWindowVisible = setVisible;
+ mShowInputFlags = setVisible ? mShowInputFlags : 0;
+ mShowInputRequested = setVisible;
+ mDecorViewVisible = setVisible;
+ if (setVisible) {
onWindowShown();
- mWindow.show();
}
}
- private void finishViews() {
+ private void finishViews(boolean finishingInput) {
if (mInputViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
- onFinishInputView(false);
+ onFinishInputView(finishingInput);
} else if (mCandidatesViewStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
- onFinishCandidatesView(false);
+ onFinishCandidatesView(finishingInput);
}
mInputViewStarted = false;
mCandidatesViewStarted = false;
@@ -1891,12 +1960,15 @@ public class InputMethodService extends AbstractInputMethodService {
}
public void hideWindow() {
- finishViews();
- if (mWindowVisible) {
+ if (DEBUG) Log.v(TAG, "CALL: hideWindow");
+ mIsPreRendered = false;
+ mWindowVisible = false;
+ finishViews(false /* finishingInput */);
+ if (mDecorViewVisible) {
mWindow.hide();
- mWindowVisible = false;
+ mDecorViewVisible = false;
onWindowHidden();
- mWindowWasVisible = false;
+ mDecorViewWasVisible = false;
}
updateFullscreenMode();
}
@@ -1956,15 +2028,8 @@ public class InputMethodService extends AbstractInputMethodService {
}
void doFinishInput() {
- if (mInputViewStarted) {
- if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
- onFinishInputView(true);
- } else if (mCandidatesViewStarted) {
- if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
- onFinishCandidatesView(true);
- }
- mInputViewStarted = false;
- mCandidatesViewStarted = false;
+ if (DEBUG) Log.v(TAG, "CALL: doFinishInput");
+ finishViews(true /* finishingInput */);
if (mInputStarted) {
if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
onFinishInput();
@@ -1984,7 +2049,7 @@ public class InputMethodService extends AbstractInputMethodService {
initialize();
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
onStartInput(attribute, restarting);
- if (mWindowVisible) {
+ if (mDecorViewVisible) {
if (mShowInputRequested) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
mInputViewStarted = true;
@@ -1995,6 +2060,31 @@ public class InputMethodService extends AbstractInputMethodService {
mCandidatesViewStarted = true;
onStartCandidatesView(mInputEditorInfo, restarting);
}
+ } else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) {
+ // Pre-render IME views and window when real EditorInfo is available.
+ // pre-render IME window and keep it invisible.
+ if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName);
+ if (mInShowWindow) {
+ Log.w(TAG, "Re-entrance in to showWindow");
+ return;
+ }
+
+ mDecorViewWasVisible = mDecorViewVisible;
+ mInShowWindow = true;
+ startViews(prepareWindow(true /* showInput */));
+
+ // compute visibility
+ mIsPreRendered = true;
+ onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+
+ // request draw for the IME surface.
+ // When IME is not pre-rendered, this will actually show the IME.
+ if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
+ mWindow.show();
+ mDecorViewWasVisible = true;
+ mInShowWindow = false;
+ } else {
+ mIsPreRendered = false;
}
}
@@ -2153,7 +2243,7 @@ public class InputMethodService extends AbstractInputMethodService {
// consume the back key.
if (doIt) requestHideSelf(0);
return true;
- } else if (mWindowVisible) {
+ } else if (mDecorViewVisible) {
if (mCandidatesVisibility == View.VISIBLE) {
// If we are showing candidates even if no input area, then
// hide them.
@@ -2180,7 +2270,6 @@ public class InputMethodService extends AbstractInputMethodService {
return mExtractEditText;
}
-
/**
* Called back when a {@link KeyEvent} is forwarded from the target application.
*
@@ -2893,8 +2982,11 @@ public class InputMethodService extends AbstractInputMethodService {
inputContentInfo.setUriToken(uriToken);
}
- private static int mapToImeWindowStatus(boolean isInputViewShown) {
- return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0);
+ private int mapToImeWindowStatus() {
+ return IME_ACTIVE
+ | (isInputViewShown()
+ ? (mCanPreRender ? (mWindowVisible ? IME_VISIBLE : IME_INVISIBLE)
+ : IME_VISIBLE) : 0);
}
/**
@@ -2904,9 +2996,10 @@ public class InputMethodService extends AbstractInputMethodService {
@Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method service state for " + this + ":");
- p.println(" mWindowCreated=" + mWindowCreated);
- p.println(" mWindowVisible=" + mWindowVisible
- + " mWindowWasVisible=" + mWindowWasVisible
+ p.println(" mViewsCreated=" + mViewsCreated);
+ p.println(" mDecorViewVisible=" + mDecorViewVisible
+ + " mDecorViewWasVisible=" + mDecorViewWasVisible
+ + " mWindowVisible=" + mWindowVisible
+ " mInShowWindow=" + mInShowWindow);
p.println(" Configuration=" + getResources().getConfiguration());
p.println(" mToken=" + mToken);
@@ -2926,6 +3019,8 @@ public class InputMethodService extends AbstractInputMethodService {
p.println(" mShowInputRequested=" + mShowInputRequested
+ " mLastShowInputRequested=" + mLastShowInputRequested
+ + " mCanPreRender=" + mCanPreRender
+ + " mIsPreRendered=" + mIsPreRendered
+ " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
p.println(" mCandidatesVisibility=" + mCandidatesVisibility
+ " mFullscreenApplied=" + mFullscreenApplied
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index b5d822672e70..6c291c25dbdf 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -17,24 +17,39 @@
package android.net;
import android.annotation.UnsupportedAppUsage;
-import android.net.NetworkUtils;
import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
* A simple object for retrieving the results of a DHCP request.
* Optimized (attempted) for that jni interface
- * TODO - remove when DhcpInfo is deprecated. Move the remaining api to LinkProperties.
+ * TODO: remove this class and replace with other existing constructs
* @hide
*/
-public class DhcpResults extends StaticIpConfiguration {
+public final class DhcpResults implements Parcelable {
private static final String TAG = "DhcpResults";
@UnsupportedAppUsage
+ public LinkAddress ipAddress;
+
+ @UnsupportedAppUsage
+ public InetAddress gateway;
+
+ @UnsupportedAppUsage
+ public final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+
+ @UnsupportedAppUsage
+ public String domains;
+
+ @UnsupportedAppUsage
public Inet4Address serverAddress;
/** Vendor specific information (from RFC 2132). */
@@ -48,23 +63,36 @@ public class DhcpResults extends StaticIpConfiguration {
@UnsupportedAppUsage
public int mtu;
- @UnsupportedAppUsage
public DhcpResults() {
super();
}
- @UnsupportedAppUsage
+ /**
+ * Create a {@link StaticIpConfiguration} based on the DhcpResults.
+ */
+ public StaticIpConfiguration toStaticIpConfiguration() {
+ final StaticIpConfiguration s = new StaticIpConfiguration();
+ // All these except dnsServers are immutable, so no need to make copies.
+ s.ipAddress = ipAddress;
+ s.gateway = gateway;
+ s.dnsServers.addAll(dnsServers);
+ s.domains = domains;
+ return s;
+ }
+
public DhcpResults(StaticIpConfiguration source) {
- super(source);
+ if (source != null) {
+ ipAddress = source.ipAddress;
+ gateway = source.gateway;
+ dnsServers.addAll(source.dnsServers);
+ domains = source.domains;
+ }
}
/** copy constructor */
- @UnsupportedAppUsage
public DhcpResults(DhcpResults source) {
- super(source);
-
+ this(source == null ? null : source.toStaticIpConfiguration());
if (source != null) {
- // All these are immutable, so no need to make copies.
serverAddress = source.serverAddress;
vendorInfo = source.vendorInfo;
leaseDuration = source.leaseDuration;
@@ -73,6 +101,14 @@ public class DhcpResults extends StaticIpConfiguration {
}
/**
+ * @see StaticIpConfiguration#getRoutes(String)
+ * @hide
+ */
+ public List<RouteInfo> getRoutes(String iface) {
+ return toStaticIpConfiguration().getRoutes(iface);
+ }
+
+ /**
* Test if this DHCP lease includes vendor hint that network link is
* metered, and sensitive to heavy data transfers.
*/
@@ -85,7 +121,11 @@ public class DhcpResults extends StaticIpConfiguration {
}
public void clear() {
- super.clear();
+ ipAddress = null;
+ gateway = null;
+ dnsServers.clear();
+ domains = null;
+ serverAddress = null;
vendorInfo = null;
leaseDuration = 0;
mtu = 0;
@@ -111,20 +151,20 @@ public class DhcpResults extends StaticIpConfiguration {
DhcpResults target = (DhcpResults)obj;
- return super.equals((StaticIpConfiguration) obj) &&
- Objects.equals(serverAddress, target.serverAddress) &&
- Objects.equals(vendorInfo, target.vendorInfo) &&
- leaseDuration == target.leaseDuration &&
- mtu == target.mtu;
+ return toStaticIpConfiguration().equals(target.toStaticIpConfiguration())
+ && Objects.equals(serverAddress, target.serverAddress)
+ && Objects.equals(vendorInfo, target.vendorInfo)
+ && leaseDuration == target.leaseDuration
+ && mtu == target.mtu;
}
- /** Implement the Parcelable interface */
+ /**
+ * Implement the Parcelable interface
+ */
public static final Creator<DhcpResults> CREATOR =
new Creator<DhcpResults>() {
public DhcpResults createFromParcel(Parcel in) {
- DhcpResults dhcpResults = new DhcpResults();
- readFromParcel(dhcpResults, in);
- return dhcpResults;
+ return readFromParcel(in);
}
public DhcpResults[] newArray(int size) {
@@ -134,19 +174,26 @@ public class DhcpResults extends StaticIpConfiguration {
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
+ toStaticIpConfiguration().writeToParcel(dest, flags);
dest.writeInt(leaseDuration);
dest.writeInt(mtu);
NetworkUtils.parcelInetAddress(dest, serverAddress, flags);
dest.writeString(vendorInfo);
}
- private static void readFromParcel(DhcpResults dhcpResults, Parcel in) {
- StaticIpConfiguration.readFromParcel(dhcpResults, in);
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static DhcpResults readFromParcel(Parcel in) {
+ final StaticIpConfiguration s = StaticIpConfiguration.CREATOR.createFromParcel(in);
+ final DhcpResults dhcpResults = new DhcpResults(s);
dhcpResults.leaseDuration = in.readInt();
dhcpResults.mtu = in.readInt();
dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in);
dhcpResults.vendorInfo = in.readString();
+ return dhcpResults;
}
// Utils for jni population - false on success
@@ -184,25 +231,70 @@ public class DhcpResults extends StaticIpConfiguration {
return false;
}
- public boolean setServerAddress(String addrString) {
- try {
- serverAddress = (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
- } catch (IllegalArgumentException|ClassCastException e) {
- Log.e(TAG, "setServerAddress failed with addrString " + addrString);
- return true;
- }
- return false;
+ public LinkAddress getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(LinkAddress ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public InetAddress getGateway() {
+ return gateway;
+ }
+
+ public void setGateway(InetAddress gateway) {
+ this.gateway = gateway;
+ }
+
+ public List<InetAddress> getDnsServers() {
+ return dnsServers;
+ }
+
+ /**
+ * Add a DNS server to this configuration.
+ */
+ public void addDnsServer(InetAddress server) {
+ dnsServers.add(server);
+ }
+
+ public String getDomains() {
+ return domains;
+ }
+
+ public void setDomains(String domains) {
+ this.domains = domains;
+ }
+
+ public Inet4Address getServerAddress() {
+ return serverAddress;
+ }
+
+ public void setServerAddress(Inet4Address addr) {
+ serverAddress = addr;
+ }
+
+ public int getLeaseDuration() {
+ return leaseDuration;
}
public void setLeaseDuration(int duration) {
leaseDuration = duration;
}
+ public String getVendorInfo() {
+ return vendorInfo;
+ }
+
public void setVendorInfo(String info) {
vendorInfo = info;
}
- public void setDomains(String newDomains) {
- domains = newDomains;
+ public int getMtu() {
+ return mtu;
+ }
+
+ public void setMtu(int mtu) {
+ this.mtu = mtu;
}
}
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index e926fda336f4..ef2269a145d0 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -39,12 +39,12 @@ import java.util.Locale;
*/
public class ProxyInfo implements Parcelable {
- private String mHost;
- private int mPort;
- private String mExclusionList;
- private String[] mParsedExclusionList;
+ private final String mHost;
+ private final int mPort;
+ private final String mExclusionList;
+ private final String[] mParsedExclusionList;
+ private final Uri mPacFileUrl;
- private Uri mPacFileUrl;
/**
*@hide
*/
@@ -96,7 +96,8 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(String host, int port, String exclList) {
mHost = host;
mPort = port;
- setExclusionList(exclList);
+ mExclusionList = exclList;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.EMPTY;
}
@@ -107,7 +108,8 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(Uri pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
@@ -121,7 +123,8 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(String pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.parse(pacFileUrl);
}
@@ -132,13 +135,22 @@ public class ProxyInfo implements Parcelable {
public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
mHost = LOCAL_HOST;
mPort = localProxyPort;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
mPacFileUrl = pacFileUrl;
}
+ private static String[] parseExclusionList(String exclusionList) {
+ if (exclusionList == null) {
+ return new String[0];
+ } else {
+ return exclusionList.toLowerCase(Locale.ROOT).split(",");
+ }
+ }
+
private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
mHost = host;
mPort = port;
@@ -159,6 +171,10 @@ public class ProxyInfo implements Parcelable {
mExclusionList = source.getExclusionListAsString();
mParsedExclusionList = source.mParsedExclusionList;
} else {
+ mHost = null;
+ mPort = 0;
+ mExclusionList = null;
+ mParsedExclusionList = null;
mPacFileUrl = Uri.EMPTY;
}
}
@@ -214,24 +230,14 @@ public class ProxyInfo implements Parcelable {
return mExclusionList;
}
- // comma separated
- private void setExclusionList(String exclusionList) {
- mExclusionList = exclusionList;
- if (mExclusionList == null) {
- mParsedExclusionList = new String[0];
- } else {
- mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(",");
- }
- }
-
/**
* @hide
*/
public boolean isValid() {
if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
- mPort == 0 ? "" : Integer.toString(mPort),
- mExclusionList == null ? "" : mExclusionList);
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
}
/**
@@ -262,7 +268,7 @@ public class ProxyInfo implements Parcelable {
sb.append("] ");
sb.append(Integer.toString(mPort));
if (mExclusionList != null) {
- sb.append(" xl=").append(mExclusionList);
+ sb.append(" xl=").append(mExclusionList);
}
} else {
sb.append("[ProxyProperties.mHost == null]");
@@ -308,8 +314,8 @@ public class ProxyInfo implements Parcelable {
*/
public int hashCode() {
return ((null == mHost) ? 0 : mHost.hashCode())
- + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
- + mPort;
+ + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ + mPort;
}
/**
@@ -352,8 +358,7 @@ public class ProxyInfo implements Parcelable {
}
String exclList = in.readString();
String[] parsedExclList = in.readStringArray();
- ProxyInfo proxyProperties =
- new ProxyInfo(host, port, exclList, parsedExclList);
+ ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
return proxyProperties;
}
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 3aa56b90251f..25bae3c57423 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -16,10 +16,11 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
-import android.net.LinkAddress;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import java.net.InetAddress;
import java.util.ArrayList;
@@ -46,17 +47,22 @@ import java.util.Objects;
*
* @hide
*/
-public class StaticIpConfiguration implements Parcelable {
+@SystemApi
+@TestApi
+public final class StaticIpConfiguration implements Parcelable {
+ /** @hide */
@UnsupportedAppUsage
public LinkAddress ipAddress;
+ /** @hide */
@UnsupportedAppUsage
public InetAddress gateway;
+ /** @hide */
@UnsupportedAppUsage
public final ArrayList<InetAddress> dnsServers;
+ /** @hide */
@UnsupportedAppUsage
public String domains;
- @UnsupportedAppUsage
public StaticIpConfiguration() {
dnsServers = new ArrayList<InetAddress>();
}
@@ -79,6 +85,41 @@ public class StaticIpConfiguration implements Parcelable {
domains = null;
}
+ public LinkAddress getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(LinkAddress ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public InetAddress getGateway() {
+ return gateway;
+ }
+
+ public void setGateway(InetAddress gateway) {
+ this.gateway = gateway;
+ }
+
+ public List<InetAddress> getDnsServers() {
+ return dnsServers;
+ }
+
+ public String getDomains() {
+ return domains;
+ }
+
+ public void setDomains(String newDomains) {
+ domains = newDomains;
+ }
+
+ /**
+ * Add a DNS server to this configuration.
+ */
+ public void addDnsServer(InetAddress server) {
+ dnsServers.add(server);
+ }
+
/**
* Returns the network routes specified by this object. Will typically include a
* directly-connected route for the IP address's local subnet and a default route. If the
@@ -86,7 +127,6 @@ public class StaticIpConfiguration implements Parcelable {
* route to the gateway as well. This configuration is arguably invalid, but it used to work
* in K and earlier, and other OSes appear to accept it.
*/
- @UnsupportedAppUsage
public List<RouteInfo> getRoutes(String iface) {
List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
if (ipAddress != null) {
@@ -107,6 +147,7 @@ public class StaticIpConfiguration implements Parcelable {
* contained in the LinkProperties will not be a complete picture of the link's configuration,
* because any configuration information that is obtained dynamically by the network (e.g.,
* IPv6 configuration) will not be included.
+ * @hide
*/
public LinkProperties toLinkProperties(String iface) {
LinkProperties lp = new LinkProperties();
@@ -124,6 +165,7 @@ public class StaticIpConfiguration implements Parcelable {
return lp;
}
+ @Override
public String toString() {
StringBuffer str = new StringBuffer();
@@ -143,6 +185,7 @@ public class StaticIpConfiguration implements Parcelable {
return str.toString();
}
+ @Override
public int hashCode() {
int result = 13;
result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode());
@@ -168,12 +211,10 @@ public class StaticIpConfiguration implements Parcelable {
}
/** Implement the Parcelable interface */
- public static Creator<StaticIpConfiguration> CREATOR =
+ public static final Creator<StaticIpConfiguration> CREATOR =
new Creator<StaticIpConfiguration>() {
public StaticIpConfiguration createFromParcel(Parcel in) {
- StaticIpConfiguration s = new StaticIpConfiguration();
- readFromParcel(s, in);
- return s;
+ return readFromParcel(in);
}
public StaticIpConfiguration[] newArray(int size) {
@@ -182,11 +223,13 @@ public class StaticIpConfiguration implements Parcelable {
};
/** Implement the Parcelable interface */
+ @Override
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface */
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(ipAddress, flags);
NetworkUtils.parcelInetAddress(dest, gateway, flags);
@@ -197,7 +240,9 @@ public class StaticIpConfiguration implements Parcelable {
dest.writeString(domains);
}
- protected static void readFromParcel(StaticIpConfiguration s, Parcel in) {
+ /** @hide */
+ public static StaticIpConfiguration readFromParcel(Parcel in) {
+ final StaticIpConfiguration s = new StaticIpConfiguration();
s.ipAddress = in.readParcelable(null);
s.gateway = NetworkUtils.unparcelInetAddress(in);
s.dnsServers.clear();
@@ -206,5 +251,6 @@ public class StaticIpConfiguration implements Parcelable {
s.dnsServers.add(NetworkUtils.unparcelInetAddress(in));
}
s.domains = in.readString();
+ return s;
}
}
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 37bf3a71ce62..dc099a46aa2a 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -509,6 +509,15 @@ public class VpnService extends Service {
}
/**
+ * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation
+ * and it is possible that some apps will ignore it.
+ */
+ public Builder setHttpProxy(ProxyInfo proxyInfo) {
+ mConfig.proxyInfo = proxyInfo;
+ return this;
+ }
+
+ /**
* Add a network address to the VPN interface. Both IPv4 and IPv6
* addresses are supported. At least one address must be set before
* calling {@link #establish}.
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index f28cdc902848..73cf94b785a7 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -16,11 +16,16 @@
package android.net.apf;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
/**
* APF program support capabilities.
*
* @hide
*/
+@SystemApi
+@TestApi
public class ApfCapabilities {
/**
* Version of APF instruction set supported for packet filtering. 0 indicates no support for
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
index 1634694cc71f..7432687e136f 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -17,11 +17,15 @@
package android.net.captiveportal;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
/**
* Result of calling isCaptivePortal().
* @hide
*/
+@SystemApi
+@TestApi
public final class CaptivePortalProbeResult {
public static final int SUCCESS_CODE = 204;
public static final int FAILED_CODE = 599;
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
index 57a926afec54..7ad4ecf2264c 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
@@ -21,21 +21,26 @@ import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/** @hide */
+@SystemApi
+@TestApi
public abstract class CaptivePortalProbeSpec {
- public static final String HTTP_LOCATION_HEADER_NAME = "Location";
-
private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName();
private static final String REGEX_SEPARATOR = "@@/@@";
private static final String SPEC_SEPARATOR = "@@,@@";
@@ -55,7 +60,9 @@ public abstract class CaptivePortalProbeSpec {
* @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}.
* @throws ParseException The string is empty, does not match the above format, or a regular
* expression is invalid for {@link Pattern#compile(String)}.
+ * @hide
*/
+ @VisibleForTesting
@NonNull
public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException,
MalformedURLException {
@@ -113,7 +120,8 @@ public abstract class CaptivePortalProbeSpec {
* <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}.
* <p>This method does not throw but ignores any entry that could not be parsed.
*/
- public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) {
+ public static Collection<CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(
+ String settingsVal) {
List<CaptivePortalProbeSpec> specs = new ArrayList<>();
if (settingsVal != null) {
for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) {
@@ -128,7 +136,7 @@ public abstract class CaptivePortalProbeSpec {
if (specs.isEmpty()) {
Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal));
}
- return specs.toArray(new CaptivePortalProbeSpec[specs.size()]);
+ return specs;
}
/**
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 954071a0ee97..3fc5e41ad990 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,6 +16,8 @@
package android.os;
+import android.Manifest.permission;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
@@ -369,4 +371,26 @@ public class BatteryManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the delay for reporting battery state as charging after device is plugged in.
+ * This allows machine-learning or heuristics to delay the reporting and the corresponding
+ * broadcast, based on battery level, charging rate, and/or other parameters.
+ *
+ * @param delayMillis the delay in milliseconds, negative value to reset.
+ *
+ * @return True if the delay was set successfully.
+ *
+ * @see ACTION_CHARGING
+ * @hide
+ */
+ @RequiresPermission(permission.POWER_SAVER)
+ @SystemApi
+ public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ try {
+ return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index eae1aa5eb750..1919fcc00a33 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -834,10 +834,10 @@ public abstract class BatteryStats implements Parcelable {
* also be bumped.
*/
static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility"
+ "other", "button", "touch", "accessibility", "attention"
};
- public static final int NUM_USER_ACTIVITY_TYPES = 4;
+ public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
public abstract void noteUserActivityLocked(int type);
public abstract boolean hasUserActivity();
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 518528dd1a5b..6f5f8072f471 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +28,7 @@ import android.os.IBinder.DeathRecipient;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Class that provides a privileged API to capture and consume bugreports.
@@ -34,7 +36,7 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
// TODO: Expose API when the implementation is more complete.
-// @SystemApi
+//@SystemApi
@SystemService(Context.BUGREPORT_SERVICE)
public class BugreportManager {
private final Context mContext;
@@ -47,55 +49,66 @@ public class BugreportManager {
}
/**
- * An interface describing the listener for bugreport progress and status.
+ * An interface describing the callback for bugreport progress and status.
*/
- public interface BugreportListener {
- /**
- * Called when there is a progress update.
- * @param progress the progress in [0.0, 100.0]
- */
- void onProgress(float progress);
-
+ public abstract static class BugreportCallback {
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
BUGREPORT_ERROR_INVALID_INPUT,
- BUGREPORT_ERROR_RUNTIME
+ BUGREPORT_ERROR_RUNTIME,
+ BUGREPORT_ERROR_USER_DENIED_CONSENT,
+ BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT
})
/** Possible error codes taking a bugreport can encounter */
- @interface BugreportErrorCode {}
+ public @interface BugreportErrorCode {}
/** The input options were invalid */
- int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
+ public static final int BUGREPORT_ERROR_INVALID_INPUT =
+ IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
/** A runtime error occured */
- int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
+ public static final int BUGREPORT_ERROR_RUNTIME =
+ IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
/** User denied consent to share the bugreport */
- int BUGREPORT_ERROR_USER_DENIED_CONSENT =
+ public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
+ /** The request to get user consent timed out. */
+ public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
+ IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+
+ /**
+ * Called when there is a progress update.
+ * @param progress the progress in [0.0, 100.0]
+ */
+ public void onProgress(float progress) {}
+
/**
* Called when taking bugreport resulted in an error.
*
- * @param errorCode the error that occurred. Possible values are
- * {@code BUGREPORT_ERROR_INVALID_INPUT},
- * {@code BUGREPORT_ERROR_RUNTIME},
- * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}.
+ * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
+ * consent to sharing the bugreport with the calling app.
+ *
+ * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
+ * out, but the bugreport could be available in the internal directory of dumpstate for
+ * manual retrieval.
*/
- void onError(@BugreportErrorCode int errorCode);
+ public void onError(@BugreportErrorCode int errorCode) {}
/**
* Called when taking bugreport finishes successfully.
*/
- void onFinished();
+ public void onFinished() {}
}
/**
* Starts a bugreport.
*
* <p>This starts a bugreport in the background. However the call itself can take several
- * seconds to return in the worst case. {@code listener} will receive progress and status
+ * seconds to return in the worst case. {@code callback} will receive progress and status
* updates.
*
* <p>The bugreport artifacts will be copied over to the given file descriptors only if the
@@ -106,19 +119,23 @@ public class BugreportManager {
* @param screenshotFd file to write the screenshot, if necessary. This should be opened
* in write-only, append mode.
* @param params options that specify what kind of a bugreport should be taken
- * @param listener callback for progress and status updates
+ * @param callback callback for progress and status updates
*/
@RequiresPermission(android.Manifest.permission.DUMP)
- public void startBugreport(@NonNull FileDescriptor bugreportFd,
- @Nullable FileDescriptor screenshotFd,
- @NonNull BugreportParams params, @NonNull BugreportListener listener) {
+ public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
+ @Nullable ParcelFileDescriptor screenshotFd,
+ @NonNull BugreportParams params,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BugreportCallback callback) {
// TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
- DumpstateListener dsListener = new DumpstateListener(listener);
-
+ DumpstateListener dsListener = new DumpstateListener(executor, callback);
try {
// Note: mBinder can get callingUid from the binder transaction.
mBinder.startBugreport(-1 /* callingUid */,
- mContext.getOpPackageName(), bugreportFd, screenshotFd,
+ mContext.getOpPackageName(),
+ (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()),
+ (screenshotFd != null
+ ? screenshotFd.getFileDescriptor() : new FileDescriptor()),
params.getMode(), dsListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -139,10 +156,12 @@ public class BugreportManager {
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
- private final BugreportListener mListener;
+ private final Executor mExecutor;
+ private final BugreportCallback mCallback;
- DumpstateListener(@Nullable BugreportListener listener) {
- mListener = listener;
+ DumpstateListener(Executor executor, @Nullable BugreportCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
}
@Override
@@ -152,19 +171,37 @@ public class BugreportManager {
@Override
public void onProgress(int progress) throws RemoteException {
- mListener.onProgress(progress);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ mCallback.onProgress(progress);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(int errorCode) throws RemoteException {
- mListener.onError(errorCode);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ mCallback.onError(errorCode);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onFinished() throws RemoteException {
+ final long identity = Binder.clearCallingIdentity();
try {
- mListener.onFinished();
+ mExecutor.execute(() -> {
+ mCallback.onFinished();
+ });
} finally {
+ Binder.restoreCallingIdentity(identity);
// The bugreport has finished. Let's shutdown the service to minimize its footprint.
cancelBugreport();
}
diff --git a/core/java/android/os/ExternalVibration.aidl b/core/java/android/os/ExternalVibration.aidl
new file mode 100644
index 000000000000..2629a401541d
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+parcelable ExternalVibration cpp_header "vibrator/ExternalVibration.h";
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
new file mode 100644
index 000000000000..69ab1d94a9e2
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An ExternalVibration represents an on-going vibration being controlled by something other than
+ * the core vibrator service.
+ *
+ * @hide
+ */
+public class ExternalVibration implements Parcelable {
+ private static final String TAG = "ExternalVibration";
+ private int mUid;
+ @NonNull
+ private String mPkg;
+ @NonNull
+ private AudioAttributes mAttrs;
+ @NonNull
+ private IExternalVibrationController mController;
+ // A token used to maintain equality comparisons when passing objects across process
+ // boundaries.
+ @NonNull
+ private IBinder mToken;
+
+ public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs,
+ @NonNull IExternalVibrationController controller) {
+ mUid = uid;
+ mPkg = Preconditions.checkNotNull(pkg);
+ mAttrs = Preconditions.checkNotNull(attrs);
+ mController = Preconditions.checkNotNull(controller);
+ mToken = new Binder();
+ }
+
+ private ExternalVibration(Parcel in) {
+ mUid = in.readInt();
+ mPkg = in.readString();
+ mAttrs = readAudioAttributes(in);
+ mController = IExternalVibrationController.Stub.asInterface(in.readStrongBinder());
+ mToken = in.readStrongBinder();
+ }
+
+ private AudioAttributes readAudioAttributes(Parcel in) {
+ int usage = in.readInt();
+ int contentType = in.readInt();
+ int capturePreset = in.readInt();
+ int flags = in.readInt();
+ AudioAttributes.Builder builder = new AudioAttributes.Builder();
+ return builder.setUsage(usage)
+ .setContentType(contentType)
+ .setCapturePreset(capturePreset)
+ .setFlags(flags)
+ .build();
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getPackage() {
+ return mPkg;
+ }
+
+ public AudioAttributes getAudioAttributes() {
+ return mAttrs;
+ }
+
+ /**
+ * Mutes the external vibration if it's playing and unmuted.
+ *
+ * @return whether the muting operation was successful
+ */
+ public boolean mute() {
+ try {
+ mController.mute();
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Failed to mute vibration stream: " + this, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Unmutes the external vibration if it's playing and muted.
+ *
+ * @return whether the unmuting operation was successful
+ */
+ public boolean unmute() {
+ try {
+ mController.unmute();
+ } catch (RemoteException e) {
+ Slog.wtf(TAG, "Failed to unmute vibration stream: " + this, e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof ExternalVibration)) {
+ return false;
+ }
+ ExternalVibration other = (ExternalVibration) o;
+ return mToken.equals(other.mToken);
+ }
+
+ @Override
+ public String toString() {
+ return "ExternalVibration{"
+ + "uid=" + mUid + ", "
+ + "pkg=" + mPkg + ", "
+ + "attrs=" + mAttrs + ", "
+ + "controller=" + mController
+ + "token=" + mController
+ + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mUid);
+ out.writeString(mPkg);
+ writeAudioAttributes(mAttrs, out, flags);
+ out.writeParcelable(mAttrs, flags);
+ out.writeStrongBinder(mController.asBinder());
+ out.writeStrongBinder(mToken);
+ }
+
+ private static void writeAudioAttributes(AudioAttributes attrs, Parcel out, int flags) {
+ out.writeInt(attrs.getUsage());
+ out.writeInt(attrs.getContentType());
+ out.writeInt(attrs.getCapturePreset());
+ out.writeInt(attrs.getAllFlags());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<ExternalVibration> CREATOR =
+ new Parcelable.Creator<ExternalVibration>() {
+ @Override
+ public ExternalVibration createFromParcel(Parcel in) {
+ return new ExternalVibration(in);
+ }
+
+ @Override
+ public ExternalVibration[] newArray(int size) {
+ return new ExternalVibration[size];
+ }
+ };
+}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 51c3c4c25ce0..629289bb7a45 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -39,6 +39,7 @@ import static android.system.OsConstants.W_OK;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.provider.DocumentsContract.Document;
import android.system.ErrnoException;
@@ -852,6 +853,7 @@ public class FileUtils {
*
* @hide
*/
+ @TestApi
public static boolean contains(File dir, File file) {
if (dir == null || file == null) return false;
return contains(dir.getAbsolutePath(), file.getAbsolutePath());
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 5bf909566f3b..efcad3ece97d 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -369,26 +369,6 @@ public class GraphicsEnvironment {
}
/**
- * Attempt to setup ANGLE with a (temporary) default rules file: b/121153494
- * True: Rules file was loaded.
- * False: Rules file was *not* loaded.
- */
- private boolean setupAngleRulesDebug(String packageName, String paths, String devOptIn) {
- // b/121153494
- // Skip APK rules file checking.
- if (!DEBUG) {
- Log.v(TAG, "Skipping loading the rules file.");
- // Fill in some default values for now, so the loader can get an answer when it asks.
- // Most importantly, we need to indicate which app we are init'ing and what the
- // developer options for it are so we can turn on ANGLE if needed.
- setAngleInfo(paths, packageName, devOptIn, null, 0, 0);
- return true;
- }
-
- return false;
- }
-
- /**
* Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
* True: APK rules file was loaded.
* False: APK rules file was *not* loaded.
@@ -425,16 +405,57 @@ public class GraphicsEnvironment {
}
/**
+ * Pull ANGLE whitelist from GlobalSettings and compare against current package
+ */
+ private boolean checkAngleWhitelist(Bundle bundle, String packageName) {
+ List<String> angleWhitelist =
+ getGlobalSettingsString(bundle,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST);
+
+ return angleWhitelist.contains(packageName);
+ }
+
+ /**
* Pass ANGLE details down to trigger enable logic
*/
public void setupAngle(Context context, Bundle bundle, String packageName) {
- String devOptIn = getDriverForPkg(bundle, packageName);
+ if (packageName.isEmpty()) {
+ Log.v(TAG, "No package name available yet, skipping ANGLE setup");
+ return;
+ }
+ String devOptIn = getDriverForPkg(bundle, packageName);
if (DEBUG) {
Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + devOptIn + "'");
}
+ // We only need to check rules if the app is whitelisted or the developer has
+ // explicitly chosen something other than default driver.
+ //
+ // The whitelist will be generated by the ANGLE APK at both boot time and
+ // ANGLE update time. It will only include apps mentioned in the rules file.
+ //
+ // If the user has set the developer option to something other than default,
+ // we need to call setupAngleRulesApk() with the package name and the developer
+ // option value (native/angle/other). Then later when we are actually trying to
+ // load a driver, GraphicsEnv::shouldUseAngle() has seen the package name before
+ // and can confidently answer yes/no based on the previously set developer
+ // option value.
+ boolean whitelisted = checkAngleWhitelist(bundle, packageName);
+ boolean defaulted = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.DEFAULT));
+ boolean rulesCheck = (whitelisted || !defaulted);
+ if (!rulesCheck) {
+ return;
+ }
+
+ if (whitelisted) {
+ Log.v(TAG, "ANGLE whitelist includes " + packageName);
+ }
+ if (!defaulted) {
+ Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
+ }
+
String anglePkgName = getAnglePackageName(context);
if (anglePkgName.isEmpty()) {
Log.e(TAG, "Failed to find ANGLE package.");
@@ -466,12 +487,6 @@ public class GraphicsEnvironment {
return;
}
- // b/121153494
- if (setupAngleRulesDebug(packageName, paths, devOptIn)) {
- // We setup ANGLE with defaults, so we're done here.
- return;
- }
-
if (setupAngleRulesApk(anglePkgName, angleInfo, context, packageName, paths, devOptIn)) {
// We setup ANGLE with rules from the APK, so we're done here.
return;
diff --git a/core/java/android/os/IExternalVibrationController.aidl b/core/java/android/os/IExternalVibrationController.aidl
new file mode 100644
index 000000000000..56da8c7cd46c
--- /dev/null
+++ b/core/java/android/os/IExternalVibrationController.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * {@hide}
+ */
+
+interface IExternalVibrationController {
+ /**
+ * A method to ask a currently playing vibration to mute (i.e. not vibrate).
+ *
+ * This method is only valid from the time that
+ * {@link IExternalVibratorService#onExternalVibrationStart} returns until
+ * {@link IExternalVibratorService#onExternalVibrationStop} returns.
+ *
+ * @return whether the mute operation was successful
+ */
+ boolean mute();
+
+ /**
+ * A method to ask a currently playing vibration to unmute (i.e. start vibrating).
+ *
+ * This method is only valid from the time that
+ * {@link IExternalVibratorService#onExternalVibrationStart} returns until
+ * {@link IExternalVibratorService#onExternalVibrationStop} returns.
+ *
+ * @return whether the unmute operation was successful
+ */
+ boolean unmute();
+}
diff --git a/core/java/android/os/IExternalVibratorService.aidl b/core/java/android/os/IExternalVibratorService.aidl
new file mode 100644
index 000000000000..666171fcbcb3
--- /dev/null
+++ b/core/java/android/os/IExternalVibratorService.aidl
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ExternalVibration;
+
+/**
+ * The communication channel by which an external system that wants to control the system
+ * vibrator can notify the vibrator subsystem.
+ *
+ * Some vibrators can be driven via multiple paths (e.g. as an audio channel) in addition to
+ * the usual interface, but we typically only want one vibration at a time playing because they
+ * don't mix well. In order to synchronize the two places where vibration might be controlled,
+ * we provide this interface so the vibrator subsystem has a chance to:
+ *
+ * 1) Decide whether the current vibration should play based on the current system policy.
+ * 2) Stop any currently on-going vibrations.
+ * {@hide}
+ */
+interface IExternalVibratorService {
+ const int SCALE_MUTE = -100;
+ const int SCALE_VERY_LOW = -2;
+ const int SCALE_LOW = -1;
+ const int SCALE_NONE = 0;
+ const int SCALE_HIGH = 1;
+ const int SCALE_VERY_HIGH = 2;
+
+ /**
+ * A method called by the external system to start a vibration.
+ *
+ * If this returns {@code SCALE_MUTE}, then the vibration should <em>not</em> play. If this
+ * returns any other scale level, then any currently playing vibration controlled by the
+ * requesting system must be muted and this vibration can begin playback.
+ *
+ * Note that the IExternalVibratorService implementation will not call mute on any currently
+ * playing external vibrations in order to avoid re-entrancy with the system on the other side.
+ *
+ * @param vibration An ExternalVibration
+ *
+ * @return {@code SCALE_MUTE} if the external vibration should not play, and any other scale
+ * level if it should.
+ */
+ int onExternalVibrationStart(in ExternalVibration vib);
+
+ /**
+ * A method called by the external system when a vibration no longer wants to play.
+ */
+ void onExternalVibrationStop(in ExternalVibration vib);
+}
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 74d434c51781..93d6f4c12128 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -119,6 +119,23 @@ interface IStatsManager {
void removeDataFetchOperation(long configKey, 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 IBinder intentSender, in String packageName);
+
+ /**
+ * Removes the active configs changed operation for the specified package name.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void removeActiveConfigsChangedOperation(in String packageName);
+
+ /**
* Removes the configuration with the matching config key. No-op if this config key does not
* exist.
*
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 63912ec327a4..d68eeeda2eb9 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -31,6 +31,7 @@ import static android.system.OsConstants.S_ISLNK;
import static android.system.OsConstants.S_ISREG;
import static android.system.OsConstants.S_IWOTH;
+import android.annotation.TestApi;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.os.MessageQueue.OnFileDescriptorEventListener;
@@ -54,6 +55,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.io.UncheckedIOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.nio.ByteOrder;
@@ -393,26 +395,41 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
* @param socket The Socket whose FileDescriptor is used to create
* a new ParcelFileDescriptor.
*
- * @return A new ParcelFileDescriptor with the FileDescriptor of the
- * specified Socket.
+ * @return A new ParcelFileDescriptor with a duped copy of the
+ * FileDescriptor of the specified Socket.
+ *
+ * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
*/
public static ParcelFileDescriptor fromSocket(Socket socket) {
FileDescriptor fd = socket.getFileDescriptor$();
- return fd != null ? new ParcelFileDescriptor(fd) : null;
+ try {
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
/**
- * Create a new ParcelFileDescriptor from the specified DatagramSocket.
+ * Create a new ParcelFileDescriptor from the specified DatagramSocket. The
+ * new ParcelFileDescriptor holds a dup of the original FileDescriptor in
+ * the DatagramSocket, so you must still close the DatagramSocket as well
+ * as the new ParcelFileDescriptor.
*
* @param datagramSocket The DatagramSocket whose FileDescriptor is used
* to create a new ParcelFileDescriptor.
*
- * @return A new ParcelFileDescriptor with the FileDescriptor of the
- * specified DatagramSocket.
+ * @return A new ParcelFileDescriptor with a duped copy of the
+ * FileDescriptor of the specified Socket.
+ *
+ * @throws UncheckedIOException if {@link #dup(FileDescriptor)} throws IOException.
*/
public static ParcelFileDescriptor fromDatagramSocket(DatagramSocket datagramSocket) {
FileDescriptor fd = datagramSocket.getFileDescriptor$();
- return fd != null ? new ParcelFileDescriptor(fd) : null;
+ try {
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
/**
@@ -542,7 +559,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
}
file.deactivate();
FileDescriptor fd = file.getFileDescriptor();
- return fd != null ? new ParcelFileDescriptor(fd) : null;
+ return fd != null ? ParcelFileDescriptor.dup(fd) : null;
}
/**
@@ -564,6 +581,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
*
* @hide
*/
+ @TestApi
public static File getFile(FileDescriptor fd) throws IOException {
try {
final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4ce760f2c4a6..7f4254e29aaa 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -336,6 +336,13 @@ public final class PowerManager {
public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
/**
+ * User activity event type: {@link android.service.attention.AttentionService} taking action
+ * on behalf of user.
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_ATTENTION = 4;
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 01d85c6c6c85..99fb608b80dc 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.ContentResolver;
@@ -25,6 +26,8 @@ import android.hardware.vibrator.V1_2.Effect;
import android.net.Uri;
import android.util.MathUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
@@ -52,26 +55,20 @@ public abstract class VibrationEffect implements Parcelable {
* A click effect.
*
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_CLICK = Effect.CLICK;
/**
* A double click effect.
*
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
/**
* A tick effect.
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_TICK = Effect.TICK;
/**
@@ -93,9 +90,7 @@ public abstract class VibrationEffect implements Parcelable {
/**
* A heavy click effect.
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
/** {@hide} */
@@ -136,6 +131,16 @@ public abstract class VibrationEffect implements Parcelable {
Effect.RINGTONE_15
};
+ /** @hide */
+ @IntDef(prefix = { "EFFECT_" }, value = {
+ EFFECT_TICK,
+ EFFECT_CLICK,
+ EFFECT_HEAVY_CLICK,
+ EFFECT_DOUBLE_CLICK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EffectType {}
+
/** @hide to prevent subclassing from outside of the framework */
public VibrationEffect() { }
@@ -219,6 +224,27 @@ public abstract class VibrationEffect implements Parcelable {
}
/**
+ * Create a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * This will fallback to a generic pattern if one exists and there does not exist a
+ * hardware-specific implementation of the effect.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createPrebaked(@EffectType int effectId) {
+ return get(effectId, true);
+ }
+
+ /**
* Get a predefined vibration effect.
*
* Predefined effects are a set of common vibration effects that should be identical, regardless
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 249b622dac4d..5dd869f46b4e 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -35,4 +35,6 @@ oneway interface IPermissionController {
void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
boolean countSystem, in RemoteCallback callback);
void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
+ void isApplicationQualifiedForRole(String roleName, String packageName,
+ in RemoteCallback callback);
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index bfcca7c10151..b59d0c7a660e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -21,6 +21,7 @@ import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -343,6 +344,28 @@ public final class PermissionControllerManager {
}
/**
+ * Check whether an application is qualified for a role.
+ *
+ * @param roleName name of the role to check for
+ * @param packageName package name of the application to check for
+ * @param executor Executor on which to invoke the callback
+ * @param callback Callback to receive the result
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ checkStringNotEmpty(roleName);
+ checkStringNotEmpty(packageName);
+ checkNotNull(executor);
+ checkNotNull(callback);
+
+ sRemoteService.scheduleRequest(new PendingIsApplicationQualifiedForRoleRequest(
+ sRemoteService, roleName, packageName, executor, callback));
+ }
+
+ /**
* A connection to the remote service
*/
static final class RemoteService extends
@@ -810,4 +833,58 @@ public final class PermissionControllerManager {
}
}
}
+
+ /**
+ * Request for {@link #isApplicationQualifiedForRole}.
+ */
+ private static final class PendingIsApplicationQualifiedForRoleRequest extends
+ AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+
+ private final @NonNull String mRoleName;
+ private final @NonNull String mPackageName;
+ private final @NonNull Consumer<Boolean> mCallback;
+
+ private final @NonNull RemoteCallback mRemoteCallback;
+
+ private PendingIsApplicationQualifiedForRoleRequest(@NonNull RemoteService service,
+ @NonNull String roleName, @NonNull String packageName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ super(service);
+
+ mRoleName = roleName;
+ mPackageName = packageName;
+ mCallback = callback;
+
+ mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
+ boolean qualified;
+ if (result != null) {
+ qualified = result.getBoolean(KEY_RESULT);
+ } else {
+ qualified = false;
+ }
+ callback.accept(qualified);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ finish();
+ }
+ }), null);
+ }
+
+ @Override
+ protected void onTimeout(RemoteService remoteService) {
+ mCallback.accept(false);
+ }
+
+ @Override
+ public void run() {
+ try {
+ getService().getServiceInterface().isApplicationQualifiedForRole(mRoleName,
+ mPackageName, mRemoteCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error checking whether application qualifies for role", e);
+ }
+ }
+ }
}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 10e8c8d5b8dd..9a58b971baf5 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.Manifest;
@@ -136,8 +137,19 @@ public abstract class PermissionControllerService extends Service {
*
* @return descriptions of the users of permissions
*/
- public abstract @NonNull List<RuntimePermissionUsageInfo>
- onPermissionUsageResult(boolean countSystem, long numMillis);
+ public abstract @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages(
+ boolean countSystem, long numMillis);
+
+ /**
+ * Check whether an application is qualified for a role.
+ *
+ * @param roleName name of the role to check for
+ * @param packageName package name of the application to check for
+ *
+ * @return whether the application is qualified for the role.
+ */
+ public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
+ @NonNull String packageName);
@Override
public final IBinder onBind(Intent intent) {
@@ -240,6 +252,20 @@ public abstract class PermissionControllerService extends Service {
PermissionControllerService.this, countSystem, numMillis,
callback));
}
+
+ @Override
+ public void isApplicationQualifiedForRole(String roleName, String packageName,
+ RemoteCallback callback) {
+ checkStringNotEmpty(roleName);
+ checkStringNotEmpty(packageName);
+ checkNotNull(callback, "callback");
+
+ enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+ mHandler.sendMessage(obtainMessage(
+ PermissionControllerService::isApplicationQualifiedForRole,
+ PermissionControllerService.this, roleName, packageName, callback));
+ }
};
}
@@ -296,7 +322,7 @@ public abstract class PermissionControllerService extends Service {
private void getPermissionUsages(boolean countSystem, long numMillis,
@NonNull RemoteCallback callback) {
List<RuntimePermissionUsageInfo> users =
- onPermissionUsageResult(countSystem, numMillis);
+ onGetPermissionUsages(countSystem, numMillis);
if (users != null && !users.isEmpty()) {
Bundle result = new Bundle();
result.putParcelableList(PermissionControllerManager.KEY_RESULT, users);
@@ -305,4 +331,12 @@ public abstract class PermissionControllerService extends Service {
callback.sendResult(null);
}
}
+
+ private void isApplicationQualifiedForRole(@NonNull String roleName,
+ @NonNull String packageName, @NonNull RemoteCallback callback) {
+ boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
+ Bundle result = new Bundle();
+ result.putBoolean(PermissionControllerManager.KEY_RESULT, qualified);
+ callback.sendResult(result);
+ }
}
diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
index 8d568c84b915..33795f81ded9 100644
--- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
+++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
@@ -78,15 +78,6 @@ public abstract class RuntimePermissionPresenterService extends Service {
public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions(
@NonNull String packageName);
- /**
- * Revokes the permission {@code permissionName} for app {@code packageName}
- *
- * @param packageName The package for which to revoke
- * @param permissionName The permission to revoke
- */
- public abstract void onRevokeRuntimePermission(@NonNull String packageName,
- @NonNull String permissionName);
-
@Override
public final IBinder onBind(Intent intent) {
return new IRuntimePermissionPresenter.Stub() {
@@ -99,17 +90,6 @@ public abstract class RuntimePermissionPresenterService extends Service {
obtainMessage(RuntimePermissionPresenterService::getAppPermissions,
RuntimePermissionPresenterService.this, packageName, callback));
}
-
- @Override
- public void revokeRuntimePermission(String packageName, String permissionName) {
- checkNotNull(packageName, "packageName");
- checkNotNull(permissionName, "permissionName");
-
- mHandler.sendMessage(
- obtainMessage(RuntimePermissionPresenterService::onRevokeRuntimePermission,
- RuntimePermissionPresenterService.this, packageName,
- permissionName));
- }
};
}
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index c167ea18f0c5..8a52f1f0eec0 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -19,6 +19,7 @@ package android.provider;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.AlarmManager;
@@ -44,6 +45,8 @@ import android.util.Log;
import com.android.internal.util.Preconditions;
+import java.util.Set;
+
/**
* <p>
* The contract between the calendar provider and applications. Contains
@@ -217,7 +220,7 @@ public final class CalendarContract {
* The intent will have its action set to
* {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras
* corresponding to the API's arguments. A calendar app intending to support
- * cross profile events viewing should handle this intent, parse the arguments
+ * cross-profile events viewing should handle this intent, parse the arguments
* and show the appropriate UI.
*
* @param context the context.
@@ -767,9 +770,10 @@ public final class CalendarContract {
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
* queried from a package that is not whitelisted by profile owner of the managed profile
- * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * via
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -802,6 +806,7 @@ public final class CalendarContract {
*
* @hide
*/
+ @TestApi
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
ACCOUNT_NAME,
ACCOUNT_TYPE,
@@ -1758,9 +1763,10 @@ public final class CalendarContract {
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
* queried from a package that is not whitelisted by profile owner of the managed profile
- * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * via
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1828,6 +1834,7 @@ public final class CalendarContract {
*
* @hide
*/
+ @TestApi
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
_SYNC_ID,
DIRTY,
@@ -1968,10 +1975,10 @@ public final class CalendarContract {
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar for the managed profile is disabled in
+ * of a managed profile, or cross-profile calendar for the managed profile is disabled in
* Settings, or this uri is queried from a package that is not whitelisted by
* profile owner of the managed profile via
- * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 25554b937065..81e1eb99336b 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -126,6 +126,7 @@ public final class ContactsContract {
* Prefix for column names that are not visible to client apps.
* @hide
*/
+ @TestApi
public static final String HIDDEN_COLUMN_PREFIX = "x_";
/**
@@ -8444,6 +8445,7 @@ public final class ContactsContract {
* nothing will be done.
* @hide
*/
+ @TestApi
public static final String UNDEMOTE_METHOD = "undemote";
/**
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6ccd2968824e..148dd910ef69 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -153,6 +153,51 @@ public final class DeviceConfig {
String OOB_WHITELIST = "oob_whitelist";
}
+ /**
+ * Namespace for activity manager related features. These features will be applied
+ * immediately upon change.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface ActivityManager {
+ String NAMESPACE = "activity_manager";
+
+ /**
+ * App compaction flags. See {@link com.android.server.am.AppCompactor}.
+ */
+ String KEY_USE_COMPACTION = "use_compaction";
+ String KEY_COMPACT_ACTION_1 = "compact_action_1";
+ String KEY_COMPACT_ACTION_2 = "compact_action_2";
+ String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
+ String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
+ String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
+ String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
+
+ /**
+ * Maximum number of cached processes. See
+ * {@link com.android.server.am.ActivityManagerConstants}.
+ */
+ String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+ }
+
+ /**
+ * Namespace for storage-related features.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface Storage {
+ String NAMESPACE = "storage";
+
+ /**
+ * If {@code 1}, enables the isolated storage feature. If {@code -1},
+ * disables the isolated storage feature. If {@code 0}, uses the default
+ * value from the build system.
+ */
+ String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+ }
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f5c442f194ba..887175a80421 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -104,6 +104,11 @@ public final class MediaStore {
*/
public static final String VOLUME_EXTERNAL = "external";
+ /** {@hide} */ @TestApi
+ public static final String SCAN_FILE_CALL = "scan_file";
+ /** {@hide} */ @TestApi
+ public static final String SCAN_VOLUME_CALL = "scan_volume";
+
/**
* 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
@@ -2992,6 +2997,7 @@ public final class MediaStore {
*
* @hide
*/
+ @TestApi
public static @NonNull File getVolumePath(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
@@ -3022,6 +3028,7 @@ public final class MediaStore {
*
* @hide
*/
+ @TestApi
public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
throws FileNotFoundException {
if (TextUtils.isEmpty(volumeName)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 95cfb6edb9fa..43e68e537ff5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -59,6 +59,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
+import android.hardware.display.ColorDisplayManager;
import android.location.LocationManager;
import android.media.AudioFormat;
import android.net.ConnectivityManager;
@@ -89,7 +90,6 @@ import android.util.MemoryIntArray;
import android.view.inputmethod.InputMethodSystemProperty;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.ColorDisplayController;
import com.android.internal.widget.ILockSettings;
import java.io.IOException;
@@ -3239,8 +3239,8 @@ public final class Settings {
private static final Validator DISPLAY_COLOR_MODE_VALIDATOR =
new SettingsValidators.InclusiveIntegerRangeValidator(
- ColorDisplayController.COLOR_MODE_NATURAL,
- ColorDisplayController.COLOR_MODE_AUTOMATIC);
+ ColorDisplayManager.COLOR_MODE_NATURAL,
+ ColorDisplayManager.COLOR_MODE_AUTOMATIC);
/**
* The amount of time in milliseconds before the device goes to sleep or begins
@@ -7802,6 +7802,9 @@ public final class Settings {
* or an activity that handles ACTION_ASSIST, or empty which means using the default
* handling.
*
+ * <p>This should be set indirectly by setting the {@link
+ * android.app.role.RoleManager#ROLE_ASSISTANT assistant role}.
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -8236,6 +8239,16 @@ public final class Settings {
private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR;
/**
+ * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right
+ * swipe).
+ *
+ * @hide
+ */
+ public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl";
+
+ private static final Validator NOTIFICATION_DISMISS_RTL_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
* Comma separated list of QS tiles that have been auto-added already.
* @hide
*/
@@ -8436,6 +8449,13 @@ public final class Settings {
new SettingsValidators.PackageNameListValidator(",");
/**
+ * Controls whether aware is enabled.
+ * @hide
+ */
+ public static final String AWARE_ENABLED = "aware_enabled";
+
+ private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -8530,6 +8550,7 @@ public final class Settings {
ASSIST_GESTURE_WAKE_ENABLED,
VR_DISPLAY_MODE,
NOTIFICATION_BADGING,
+ NOTIFICATION_DISMISS_RTL,
QS_AUTO_ADDED_TILES,
SCREENSAVER_ENABLED,
SCREENSAVER_COMPONENTS,
@@ -8559,6 +8580,7 @@ public final class Settings {
SKIP_GESTURE,
SILENCE_GESTURE,
THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ AWARE_ENABLED,
};
/**
@@ -8691,6 +8713,7 @@ public final class Settings {
VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR);
VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR);
VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_DISMISS_RTL, NOTIFICATION_DISMISS_RTL_VALIDATOR);
VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR);
VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR);
VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR);
@@ -8731,6 +8754,7 @@ public final class Settings {
VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
+ VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR);
}
/**
@@ -8762,6 +8786,7 @@ public final class Settings {
CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
+ CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
@@ -12129,6 +12154,13 @@ public final class Settings {
"angle_gl_driver_selection_values";
/**
+ * List of package names that should check ANGLE rules
+ * @hide
+ */
+ public static final String GLOBAL_SETTINGS_ANGLE_WHITELIST =
+ "angle_whitelist";
+
+ /**
* Game Update Package global preference for all Apps.
* 0 = Default
* 1 = All Apps use Game Update Package
@@ -12999,48 +13031,37 @@ public final class Settings {
"sms_access_restriction_enabled";
/**
- * If set to 1, an app must have the READ_PRIVILEGED_PHONE_STATE permission (or be a device
- * / profile owner with the READ_PHONE_STATE permission) to access device identifiers.
- *
- * STOPSHIP: Remove this once we ship with the new device identifier check enabled.
- *
- * @hide
- */
- public static final String PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED =
- "privileged_device_identifier_check_enabled";
-
- /**
- * If set to 1, an app that is targeting Q and does not meet the new requirements to access
- * device identifiers will receive a SecurityException.
+ * If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
+ * permission check for 3P apps.
*
* STOPSHIP: Remove this once we ship with the new device identifier check enabled.
*
* @hide
*/
- public static final String PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED =
- "privileged_device_identifier_target_q_behavior_enabled";
+ public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED =
+ "privileged_device_identifier_3p_check_relaxed";
/**
* If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
- * permission check for 3P apps.
+ * permission check for preloaded non-privileged apps.
*
* STOPSHIP: Remove this once we ship with the new device identifier check enabled.
*
* @hide
*/
- public static final String PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED =
- "privileged_device_identifier_3p_check_relaxed";
+ public static final String PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED =
+ "privileged_device_identifier_non_priv_check_relaxed";
/**
* If set to 1, the device identifier check will be relaxed to the previous READ_PHONE_STATE
- * permission check for preloaded non-privileged apps.
+ * permission check for preloaded privileged apps.
*
* STOPSHIP: Remove this once we ship with the new device identifier check enabled.
*
* @hide
*/
- public static final String PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED =
- "privileged_device_identifier_non_priv_check_relaxed";
+ public static final String PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED =
+ "privileged_device_identifier_priv_check_relaxed";
/**
* If set to 1, SettingsProvider's restoreAnyVersion="true" attribute will be ignored
@@ -14260,6 +14281,17 @@ public final class Settings {
*/
public static final String APPOP_HISTORY_PARAMETERS =
"appop_history_parameters";
+
+ /**
+ * Delay for sending ACTION_CHARGING after device is plugged in.
+ * This is used as an override for constants defined in BatteryStatsImpl for
+ * ease of experimentation.
+ *
+ * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS
+ * @hide
+ */
+ public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY =
+ "battery_charging_state_update_delay";
}
/**
@@ -14583,6 +14615,17 @@ public final class Settings {
"android.settings.panel.action.INTERNET_CONNECTIVITY";
/**
+ * Activity Action: Show a settings dialog containing NFC-related settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NFC =
+ "android.settings.panel.action.NFC";
+
+ /**
* Activity Action: Show a settings dialog containing all volume streams.
* <p>
* Input: Nothing.
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 140336e93357..dce5d56e24a4 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -18,6 +18,7 @@ package android.provider;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -289,6 +290,7 @@ public class VoicemailContract {
* Path to the media content file. Internal only field.
* @hide
*/
+ @TestApi
public static final String _DATA = "_data";
// Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index de532b74375f..b6788f578bd6 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,8 +15,6 @@
*/
package android.service.notification;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Notification;
import android.os.Bundle;
import android.os.Parcel;
@@ -24,10 +22,7 @@ import android.os.Parcelable;
/**
* Ranking updates from the Assistant.
- * @hide
*/
-@SystemApi
-@TestApi
public final class Adjustment implements Parcelable {
private final String mPackage;
private final String mKey;
@@ -39,6 +34,7 @@ public final class Adjustment implements Parcelable {
* Data type: ArrayList of {@code String}, where each is a representation of a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
* See {@link android.app.Notification.Builder#addPerson(String)}.
+ * @hide
*/
public static final String KEY_PEOPLE = "key_people";
/**
@@ -46,6 +42,7 @@ public final class Adjustment implements Parcelable {
* users. If a user chooses to snooze a notification until one of these criterion, the
* assistant will be notified via
* {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
+ * @hide
*/
public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
/**
@@ -112,7 +109,7 @@ public final class Adjustment implements Parcelable {
mUser = user;
}
- protected Adjustment(Parcel in) {
+ private Adjustment(Parcel in) {
if (in.readInt() == 1) {
mPackage = in.readString();
} else {
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index ad34ab3bddb1..e93b1580bc66 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -21,8 +21,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.admin.DevicePolicyManager;
@@ -61,11 +59,7 @@ import java.util.List;
* <p>
* All callbacks are called on the main thread.
* </p>
- *
- * @hide
*/
-@SystemApi
-@TestApi
public abstract class NotificationAssistantService extends NotificationListenerService {
private static final String TAG = "NotificationAssistants";
@@ -109,6 +103,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS
*
* @param sbn the notification to snooze
* @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
+ * @hide
*/
abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
String snoozeCriterionId);
@@ -250,6 +245,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS
* {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
* notification.
* @param key The key of the notification to snooze
+ * @hide
*/
public final void unsnoozeNotification(String key) {
if (!isBound()) return;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0e63cd37f3f2..c734b630759b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1617,14 +1617,16 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * @hide
+ * Returns a list of smart {@link Notification.Action} that can be added by the
+ * {@link NotificationAssistantService}
*/
public List<Notification.Action> getSmartActions() {
return mSmartActions;
}
/**
- * @hide
+ * Returns a list of smart replies that can be added by the
+ * {@link NotificationAssistantService}
*/
public List<CharSequence> getSmartReplies() {
return mSmartReplies;
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index e5f3dfbe2a1c..814b4772a395 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,8 +16,6 @@
package android.service.notification;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.RemoteInput;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,11 +23,6 @@ import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/**
- * @hide
- */
-@TestApi
-@SystemApi
public final class NotificationStats implements Parcelable {
private boolean mSeen;
@@ -105,7 +98,7 @@ public final class NotificationStats implements Parcelable {
public NotificationStats() {
}
- protected NotificationStats(Parcel in) {
+ private NotificationStats(Parcel in) {
mSeen = in.readByte() != 0;
mExpanded = in.readByte() != 0;
mDirectReplied = in.readByte() != 0;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0edcb3d8eb6a..db9351b030df 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -40,6 +40,7 @@ public class FeatureFlagUtils {
public static final String SAFETY_HUB = "settings_safety_hub";
public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
+ public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled";
private static final Map<String, String> DEFAULT_FLAGS;
private static final Set<String> OBSERVABLE_FLAGS;
@@ -51,15 +52,16 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put("settings_mobile_network_v2", "true");
DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false");
DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
- DEFAULT_FLAGS.put("settings_slice_injection", "false");
+ DEFAULT_FLAGS.put("settings_slice_injection", "true");
DEFAULT_FLAGS.put("settings_systemui_theme", "true");
- DEFAULT_FLAGS.put("settings_wifi_dpp", "false");
- DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false");
- DEFAULT_FLAGS.put("settings_wifi_sharing", "false");
+ DEFAULT_FLAGS.put("settings_wifi_dpp", "true");
+ DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true");
+ DEFAULT_FLAGS.put("settings_wifi_sharing", "true");
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
DEFAULT_FLAGS.put(SAFETY_HUB, "false");
DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
+ DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "false");
OBSERVABLE_FLAGS = new HashSet<>();
OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f58efc900427..e3a6bd7a6949 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@ import android.app.KeyguardManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.ColorSpace;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -953,6 +954,24 @@ public final class Display {
}
/**
+ * Returns the preferred wide color space of the Display.
+ * The returned wide gamut color space is based on hardware capability and
+ * is preferred by the composition pipeline.
+ * Returns null if the display doesn't support wide color gamut.
+ * {@link Display#isWideColorGamut()}.
+ */
+ @Nullable
+ public ColorSpace getPreferredWideGamutColorSpace() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ if (mDisplayInfo.isWideColorGamut()) {
+ return mGlobal.getPreferredWideGamutColorSpace();
+ }
+ return null;
+ }
+ }
+
+ /**
* Gets the supported color modes of this device.
* @hide
*/
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index dd6231d9c5b0..2142c36f8803 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,17 +16,22 @@
package android.view;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Property;
import android.util.SparseArray;
+import android.view.InsetsState.InternalInsetType;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetType;
-import android.view.InsetsState.InternalInsetType;
import com.android.internal.annotations.VisibleForTesting;
@@ -39,6 +44,41 @@ import java.util.ArrayList;
*/
public class InsetsController implements WindowInsetsController {
+ // TODO: Use animation scaling and more optimal duration.
+ private static final int ANIMATION_DURATION_MS = 400;
+ private static final int DIRECTION_NONE = 0;
+ private static final int DIRECTION_SHOW = 1;
+ private static final int DIRECTION_HIDE = 2;
+ @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
+ private @interface AnimationDirection{}
+
+ /**
+ * Translation animation evaluator.
+ */
+ private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
+ 0,
+ (int) (startValue.top + fraction * (endValue.top - startValue.top)),
+ 0,
+ (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+ /**
+ * Linear animation property
+ */
+ private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
+ InsetsProperty() {
+ super(Insets.class, "Insets");
+ }
+
+ @Override
+ public Insets get(WindowInsetsAnimationController object) {
+ return object.getCurrentInsets();
+ }
+ @Override
+ public void set(WindowInsetsAnimationController object, Insets value) {
+ object.changeInsets(value);
+ }
+ }
+
private final String TAG = "InsetsControllerImpl";
private final InsetsState mState = new InsetsState();
@@ -58,6 +98,8 @@ public class InsetsController implements WindowInsetsController {
private final Rect mLastLegacyContentInsets = new Rect();
private final Rect mLastLegacyStableInsets = new Rect();
+ private ObjectAnimator mAnimator;
+ private @AnimationDirection int mAnimationDirection;
public InsetsController(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
@@ -122,7 +164,10 @@ public class InsetsController implements WindowInsetsController {
public void onControlsChanged(InsetsSourceControl[] activeControls) {
if (activeControls != null) {
for (InsetsSourceControl activeControl : activeControls) {
- mTmpControlArray.put(activeControl.getType(), activeControl);
+ if (activeControl != null) {
+ // TODO(b/122982984): Figure out why it can be null.
+ mTmpControlArray.put(activeControl.getType(), activeControl);
+ }
}
}
@@ -146,18 +191,40 @@ public class InsetsController implements WindowInsetsController {
@Override
public void show(@InsetType int types) {
+ int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- getSourceConsumer(internalTypes.valueAt(i)).show();
+ InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+ if (mAnimationDirection == DIRECTION_HIDE) {
+ // Only one animator (with multiple InsetType) can run at a time.
+ // previous one should be cancelled for simplicity.
+ cancelExistingAnimation();
+ } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) {
+ // no-op: already shown or animating in.
+ // TODO: When we have more than one types: handle specific case when
+ // show animation is going on, but the current type is not becoming visible.
+ continue;
+ }
+ typesReady |= InsetsState.toPublicType(consumer.getType());
}
+ applyAnimation(typesReady, true /* show */);
}
@Override
public void hide(@InsetType int types) {
+ int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- getSourceConsumer(internalTypes.valueAt(i)).hide();
+ InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+ if (mAnimationDirection == DIRECTION_SHOW) {
+ cancelExistingAnimation();
+ } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) {
+ // no-op: already hidden or animating out.
+ continue;
+ }
+ typesReady |= InsetsState.toPublicType(consumer.getType());
}
+ applyAnimation(typesReady, false /* show */);
}
@Override
@@ -226,6 +293,79 @@ public class InsetsController implements WindowInsetsController {
}
}
+ private void applyAnimation(@InsetType final int types, boolean show) {
+ if (types == 0) {
+ // nothing to animate.
+ return;
+ }
+ WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ mAnimator = ObjectAnimator.ofObject(
+ controller,
+ new InsetsProperty(),
+ sEvaluator,
+ show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+ show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+ );
+ mAnimator.setDuration(ANIMATION_DURATION_MS);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ onAnimationFinish();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationFinish();
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ public void onCancelled() {}
+
+ private void onAnimationFinish() {
+ mAnimationDirection = DIRECTION_NONE;
+ if (show) {
+ showOnAnimationEnd(types);
+ } else {
+ hideOnAnimationEnd(types);
+ }
+ }
+ };
+ // TODO: Instead of clearing this here, properly wire up
+ // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
+ mAnimationControls.clear();
+ controlWindowInsetsAnimation(types, listener);
+ }
+
+ private void hideOnAnimationEnd(@InsetType int types) {
+ final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+ for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ getSourceConsumer(internalTypes.valueAt(i)).hide();
+ }
+ }
+
+ private void showOnAnimationEnd(@InsetType int types) {
+ final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+ for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ getSourceConsumer(internalTypes.valueAt(i)).show();
+ }
+ }
+
+ /**
+ * Cancel on-going animation to show/hide {@link InsetType}.
+ */
+ @VisibleForTesting
+ public void cancelExistingAnimation() {
+ mAnimationDirection = DIRECTION_NONE;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.println(prefix); pw.println("InsetsController:");
mState.dump(prefix + " ", pw);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 145b09763676..7937cb69b80e 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,8 +17,8 @@
package android.view;
import android.annotation.Nullable;
-import android.view.SurfaceControl.Transaction;
import android.view.InsetsState.InternalInsetType;
+import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
@@ -89,6 +89,11 @@ public class InsetsSourceConsumer {
return true;
}
+ @VisibleForTesting
+ public boolean isVisible() {
+ return mVisible;
+ }
+
private void setVisible(boolean visible) {
if (mVisible == visible) {
return;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 529776e542ee..a6af1a296faf 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,7 +16,10 @@
package android.view;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.SIZE;
import static android.view.WindowInsets.Type.indexOf;
import android.annotation.IntDef;
@@ -124,9 +127,10 @@ public class InsetsState implements Parcelable {
@Nullable @InsetSide SparseIntArray typeSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
+ boolean[] typeVisibilityMap = new boolean[SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
- if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
+ if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
&& legacyContentInsets != null && legacyStableInsets != null) {
WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
@@ -136,22 +140,29 @@ public class InsetsState implements Parcelable {
if (source == null) {
continue;
}
+ if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
+ && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) {
+ typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
+ continue;
+ }
+
processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
- typeSideMap);
+ typeSideMap, typeVisibilityMap);
// IME won't be reported in max insets as the size depends on the EditorInfo of the IME
// target.
if (source.getType() != TYPE_IME) {
processSource(source, relativeFrameMax, true /* ignoreVisibility */,
- typeMaxInsetsMap, null /* typeSideMap */);
+ typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
}
}
- return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, isScreenRound,
+ return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
alwaysConsumeNavBar, cutout);
}
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
- Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap) {
+ Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
+ @Nullable boolean[] typeVisibilityMap) {
Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
int index = indexOf(toPublicType(source.getType()));
@@ -162,6 +173,10 @@ public class InsetsState implements Parcelable {
typeInsetsMap[index] = Insets.max(existing, insets);
}
+ if (typeVisibilityMap != null) {
+ typeVisibilityMap[index] = source.isVisible();
+ }
+
if (typeSideMap != null && !Insets.NONE.equals(insets)) {
@InsetSide int insetSide = getInsetSide(insets);
if (insetSide != INSET_SIDE_UNKNWON) {
diff --git a/core/java/android/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index 3c9ce788b706..6f5a85d210af 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -18,7 +18,6 @@ package android.view;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityOptions;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -52,6 +51,7 @@ public class RemoteAnimationAdapter implements Parcelable {
private final IRemoteAnimationRunner mRunner;
private final long mDuration;
private final long mStatusBarTransitionDelay;
+ private final boolean mChangeNeedsSnapshot;
/** @see #getCallingPid */
private int mCallingPid;
@@ -59,21 +59,31 @@ public class RemoteAnimationAdapter implements Parcelable {
/**
* @param runner The interface that gets notified when we actually need to start the animation.
* @param duration The duration of the animation.
+ * @param changeNeedsSnapshot For change transitions, whether this should create a snapshot by
+ * screenshotting the task.
* @param statusBarTransitionDelay The desired delay for all visual animations in the
* status bar caused by this app animation in millis.
*/
@UnsupportedAppUsage
public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
- long statusBarTransitionDelay) {
+ long statusBarTransitionDelay, boolean changeNeedsSnapshot) {
mRunner = runner;
mDuration = duration;
+ mChangeNeedsSnapshot = changeNeedsSnapshot;
mStatusBarTransitionDelay = statusBarTransitionDelay;
}
+ @UnsupportedAppUsage
+ public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration,
+ long statusBarTransitionDelay) {
+ this(runner, duration, statusBarTransitionDelay, false /* changeNeedsSnapshot */);
+ }
+
public RemoteAnimationAdapter(Parcel in) {
mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder());
mDuration = in.readLong();
mStatusBarTransitionDelay = in.readLong();
+ mChangeNeedsSnapshot = in.readBoolean();
}
public IRemoteAnimationRunner getRunner() {
@@ -88,6 +98,10 @@ public class RemoteAnimationAdapter implements Parcelable {
return mStatusBarTransitionDelay;
}
+ public boolean getChangeNeedsSnapshot() {
+ return mChangeNeedsSnapshot;
+ }
+
/**
* To be called by system_server to keep track which pid is running this animation.
*/
@@ -112,6 +126,7 @@ public class RemoteAnimationAdapter implements Parcelable {
dest.writeStrongInterface(mRunner);
dest.writeLong(mDuration);
dest.writeLong(mStatusBarTransitionDelay);
+ dest.writeBoolean(mChangeNeedsSnapshot);
}
public static final Creator<RemoteAnimationAdapter> CREATOR
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 863b717008d2..4032a6b84801 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -46,10 +46,9 @@ import android.hardware.display.DisplayedContentSamplingAttributes;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -60,6 +59,7 @@ import dalvik.system.CloseGuard;
import libcore.util.NativeAllocationRegistry;
import java.io.Closeable;
+import java.nio.ByteBuffer;
/**
* Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
@@ -75,7 +75,7 @@ public final class SurfaceControl implements Parcelable {
private static final String TAG = "SurfaceControl";
private static native long nativeCreate(SurfaceSession session, String name,
- int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid)
+ int w, int h, int format, int flags, long parentObject, Parcel metadata)
throws OutOfResourcesException;
private static native long nativeReadFromParcel(Parcel in);
private static native long nativeCopyFromSurfaceControl(long nativeObject);
@@ -182,6 +182,7 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken,
IBinder toToken);
private static native boolean nativeGetProtectedContentSupport();
+ private static native void nativeSetMetadata(long transactionObj, int key, Parcel data);
private final CloseGuard mCloseGuard = CloseGuard.get();
private String mName;
@@ -413,6 +414,24 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * owner UID.
+ * @hide
+ */
+ public static final int METADATA_OWNER_UID = 1;
+
+ /**
+ * Window type as per {@link WindowManager.LayoutParams}.
+ * @hide
+ */
+ public static final int METADATA_WINDOW_TYPE = 2;
+
+ /**
+ * Task id to allow association between surfaces and task.
+ * @hide
+ */
+ public static final int METADATA_TASK_ID = 3;
+
+ /**
* Builder class for {@link SurfaceControl} objects.
*/
public static class Builder {
@@ -423,8 +442,7 @@ public final class SurfaceControl implements Parcelable {
private int mFormat = PixelFormat.OPAQUE;
private String mName;
private SurfaceControl mParent;
- private int mWindowType = -1;
- private int mOwnerUid = -1;
+ private SparseIntArray mMetadata;
/**
* Begin building a SurfaceControl with a given {@link SurfaceSession}.
@@ -455,8 +473,8 @@ public final class SurfaceControl implements Parcelable {
throw new IllegalArgumentException(
"Only buffer layers can set a valid buffer size.");
}
- return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
- mFlags, mParent, mWindowType, mOwnerUid);
+ return new SurfaceControl(
+ mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata);
}
/**
@@ -581,23 +599,17 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * Set surface metadata.
+ * Sets a metadata int.
*
- * Currently these are window-types as per {@link WindowManager.LayoutParams} and
- * owner UIDs. Child surfaces inherit their parents
- * metadata so only the WindowManager needs to set this on root Surfaces.
- *
- * @param windowType A window-type
- * @param ownerUid UID of the window owner.
+ * @param key metadata key
+ * @param data associated data
* @hide
*/
- public Builder setMetadata(int windowType, int ownerUid) {
- if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
- throw new UnsupportedOperationException(
- "It only makes sense to set Surface metadata from the WindowManager");
+ public Builder setMetadata(int key, int data) {
+ if (mMetadata == null) {
+ mMetadata = new SparseIntArray();
}
- mWindowType = windowType;
- mOwnerUid = ownerUid;
+ mMetadata.put(key, data);
return this;
}
@@ -682,13 +694,12 @@ public final class SurfaceControl implements Parcelable {
* @param h The surface initial height.
* @param flags The surface creation flags. Should always include {@link #HIDDEN}
* in the creation flags.
- * @param windowType The type of the window as specified in WindowManager.java.
- * @param ownerUid A unique per-app ID.
+ * @param metadata Initial metadata.
*
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
- SurfaceControl parent, int windowType, int ownerUid)
+ SurfaceControl parent, SparseIntArray metadata)
throws OutOfResourcesException, IllegalArgumentException {
if (name == null) {
throw new IllegalArgumentException("name must not be null");
@@ -706,8 +717,21 @@ public final class SurfaceControl implements Parcelable {
mName = name;
mWidth = w;
mHeight = h;
- mNativeObject = nativeCreate(session, name, w, h, format, flags,
- parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
+ Parcel metaParcel = Parcel.obtain();
+ try {
+ if (metadata != null && metadata.size() > 0) {
+ metaParcel.writeInt(metadata.size());
+ for (int i = 0; i < metadata.size(); ++i) {
+ metaParcel.writeInt(metadata.keyAt(i));
+ metaParcel.writeByteArray(
+ ByteBuffer.allocate(4).putInt(metadata.valueAt(i)).array());
+ }
+ }
+ mNativeObject = nativeCreate(session, name, w, h, format, flags,
+ parent != null ? parent.mNativeObject : 0, metaParcel);
+ } finally {
+ metaParcel.recycle();
+ }
if (mNativeObject == 0) {
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
@@ -2326,6 +2350,30 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Sets an arbitrary piece of metadata on the surface. This is a helper for int data.
+ * @hide
+ */
+ public Transaction setMetadata(int key, int data) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeInt(data);
+ try {
+ setMetadata(key, parcel);
+ } finally {
+ parcel.recycle();
+ }
+ return this;
+ }
+
+ /**
+ * Sets an arbitrary piece of metadata on the surface.
+ * @hide
+ */
+ public Transaction setMetadata(int key, Parcel data) {
+ nativeSetMetadata(mNativeObject, key, data);
+ return this;
+ }
+
+ /**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 483280e36bd1..cd3decf4e981 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3985,6 +3985,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
/**
+ * If a MotionEvent has CLASSIFICATION_AMBIGUOUS_GESTURE set, then certain the default
+ * long press action will be inhibited. However, to account for the possibility of incorrect
+ * classification, the default long press timeout will instead be increased for some situations
+ * by the following factor.
+ * Likewise, the touch slop for allowing long press will be increased when gesture is uncertain.
+ */
+ private static final int AMBIGUOUS_GESTURE_MULTIPLIER = 2;
+
+ /**
* Controls the over-scroll mode for this view.
* See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
* {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
@@ -9058,35 +9067,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid="
+ isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+ ", visible=" + (getVisibility() == VISIBLE)
- + ": alreadyNotifiedAppeared="
- + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0));
+ + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+ & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+ + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
+ & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
}
return;
}
- // All good: notify it...
- final ViewStructure structure = session.newViewStructure(this);
- onProvideContentCaptureStructure(structure, /* flags= */ 0);
- session.notifyViewAppeared(structure);
- // ...and set the flags
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
+
+ // The code below doesn't take much for a unique view, but it's called for all views
+ // the first time the view hiearchy is laid off, which could acccumulative delay the
+ // initial layout. Hence, we're postponing it to a later stage - it might still cost a
+ // lost frame (or more), but that jank cost would only happen after the 1st layout.
+ Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+ final ViewStructure structure = session.newViewStructure(this);
+ onProvideContentCaptureStructure(structure, /* flags= */ 0);
+ session.notifyViewAppeared(structure);
+ }, /* token= */ null);
} else {
if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
|| (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
- Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this
- + ": notifiedAppeared="
- + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+ Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid="
+ + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+ + ", visible=" + (getVisibility() == VISIBLE)
+ + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+ & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+ ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
& PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
}
return;
}
- // All good: notify it...
- session.notifyViewDisappeared(getAutofillId());
- // ...and set the flags
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+ Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT,
+ () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null);
}
}
@@ -9154,7 +9171,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ContentCaptureSession session = null;
if (mParent instanceof View) {
- session = ((View) mParent).getContentCaptureSession();
+ session = ((View) mParent).getContentCaptureSession(ccm);
}
return session != null ? session : ccm.getMainContentCaptureSession();
@@ -14780,8 +14797,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
drawableHotspotChanged(x, y);
}
+ final int motionClassification = event.getClassification();
+ final boolean ambiguousGesture =
+ motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
+ int touchSlop = mTouchSlop;
+ if (ambiguousGesture && hasPendingLongPressCallback()) {
+ if (!pointInView(x, y, touchSlop)) {
+ // The default action here is to cancel long press. But instead, we
+ // just extend the timeout here, in case the classification
+ // stays ambiguous.
+ removeLongPressCallback();
+ long delay = ViewConfiguration.getLongPressTimeout()
+ * AMBIGUOUS_GESTURE_MULTIPLIER;
+ // Subtract the time already spent
+ delay -= event.getEventTime() - event.getDownTime();
+ checkForLongClick(delay, x, y);
+ }
+ touchSlop *= AMBIGUOUS_GESTURE_MULTIPLIER;
+ }
+
// Be lenient about moving outside of buttons
- if (!pointInView(x, y, mTouchSlop)) {
+ if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
@@ -14791,6 +14827,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
+
+ final boolean deepPress =
+ motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
+ if (deepPress && hasPendingLongPressCallback()) {
+ // process the long click action immediately
+ removeLongPressCallback();
+ checkForLongClick(0 /* send immediately */, x, y);
+ }
+
break;
}
@@ -14825,6 +14870,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Return true if the long press callback is scheduled to run sometime in the future.
+ * Return false if there is no scheduled long press callback at the moment.
+ */
+ private boolean hasPendingLongPressCallback() {
+ if (mPendingCheckForLongPress == null) {
+ return false;
+ }
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo == null) {
+ return false;
+ }
+ return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress);
+ }
+
+ /**
* Remove the pending click action
*/
@UnsupportedAppUsage
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index e8088303eac7..c1536ae2b4ae 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -66,6 +66,7 @@ public final class WindowInsets {
private final Insets[] mTypeInsetsMap;
private final Insets[] mTypeMaxInsetsMap;
+ private final boolean[] mTypeVisibilityMap;
@Nullable private Rect mTempRect;
private final boolean mIsRound;
@@ -106,6 +107,7 @@ public final class WindowInsets {
public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect,
boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
+ createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
isRound, alwaysConsumeNavBar, displayCutout);
}
@@ -122,7 +124,9 @@ public final class WindowInsets {
* @hide
*/
public WindowInsets(@Nullable Insets[] typeInsetsMap,
- @Nullable Insets[] typeMaxInsetsMap, boolean isRound,
+ @Nullable Insets[] typeMaxInsetsMap,
+ boolean[] typeVisibilityMap,
+ boolean isRound,
boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -134,6 +138,7 @@ public final class WindowInsets {
? new Insets[SIZE]
: typeMaxInsetsMap.clone();
+ mTypeVisibilityMap = typeVisibilityMap;
mIsRound = isRound;
mAlwaysConsumeNavBar = alwaysConsumeNavBar;
@@ -148,8 +153,8 @@ public final class WindowInsets {
* @param src Source to copy insets from
*/
public WindowInsets(WindowInsets src) {
- this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mIsRound, src.mAlwaysConsumeNavBar,
- displayCutoutCopyConstructorArgument(src));
+ this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound,
+ src.mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(src));
}
private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -200,7 +205,7 @@ public final class WindowInsets {
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(createCompatTypeMap(systemWindowInsets), null, false, false, null);
+ this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null);
}
/**
@@ -225,6 +230,20 @@ public final class WindowInsets {
typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
}
+ private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) {
+ boolean[] typeVisibilityMap = new boolean[SIZE];
+ if (typeInsetMap == null) {
+ return typeVisibilityMap;
+ }
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ int index = indexOf(i);
+ if (!Insets.NONE.equals(typeInsetMap[index])) {
+ typeVisibilityMap[index] = true;
+ }
+ }
+ return typeVisibilityMap;
+ }
+
/**
* Used to provide a safe copy of the system window insets to pass through
* to the existing fitSystemWindows method and other similar internals.
@@ -297,6 +316,27 @@ public final class WindowInsets {
}
/**
+ * Returns whether a set of windows that may cause insets is currently visible on screen,
+ * regardless of whether it actually overlaps with this window.
+ *
+ * @param typeMask Bit mask of {@link InsetType}s to query visibility status.
+ * @return {@code true} if and only if all windows included in {@code typeMask} are currently
+ * visible on screen.
+ * @hide pending unhide
+ */
+ public boolean isVisible(@InsetType int typeMask) {
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ if (!mTypeVisibilityMap[indexOf(i)]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns the left system window inset in pixels.
*
* <p>The system window inset represents the area of a full-screen window that is
@@ -392,6 +432,7 @@ public final class WindowInsets {
public WindowInsets consumeDisplayCutout() {
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+ mTypeVisibilityMap,
mIsRound, mAlwaysConsumeNavBar,
null /* displayCutout */);
}
@@ -437,6 +478,7 @@ public final class WindowInsets {
@NonNull
public WindowInsets consumeSystemWindowInsets() {
return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+ mTypeVisibilityMap,
mIsRound, mAlwaysConsumeNavBar,
displayCutoutCopyConstructorArgument(this));
}
@@ -594,7 +636,7 @@ public final class WindowInsets {
@NonNull
public WindowInsets consumeStableInsets() {
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
- mIsRound, mAlwaysConsumeNavBar,
+ mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar,
displayCutoutCopyConstructorArgument(this));
}
@@ -671,6 +713,7 @@ public final class WindowInsets {
mStableInsetsConsumed
? null
: insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
+ mTypeVisibilityMap,
mIsRound, mAlwaysConsumeNavBar,
mDisplayCutoutConsumed
? null
@@ -692,14 +735,15 @@ public final class WindowInsets {
&& mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
&& Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
&& Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
+ && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
&& Objects.equals(mDisplayCutout, that.mDisplayCutout);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
- mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
- mStableInsetsConsumed, mDisplayCutoutConsumed);
+ Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mAlwaysConsumeNavBar,
+ mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed);
}
@@ -754,6 +798,7 @@ public final class WindowInsets {
private final Insets[] mTypeInsetsMap;
private final Insets[] mTypeMaxInsetsMap;
+ private final boolean[] mTypeVisibilityMap;
private boolean mSystemInsetsConsumed = true;
private boolean mStableInsetsConsumed = true;
@@ -768,6 +813,7 @@ public final class WindowInsets {
public Builder() {
mTypeInsetsMap = new Insets[SIZE];
mTypeMaxInsetsMap = new Insets[SIZE];
+ mTypeVisibilityMap = new boolean[SIZE];
}
/**
@@ -778,6 +824,7 @@ public final class WindowInsets {
public Builder(WindowInsets insets) {
mTypeInsetsMap = insets.mTypeInsetsMap.clone();
mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone();
+ mTypeVisibilityMap = insets.mTypeVisibilityMap.clone();
mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
mStableInsetsConsumed = insets.mStableInsetsConsumed;
mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
@@ -862,6 +909,29 @@ public final class WindowInsets {
}
/**
+ * Sets whether windows that can cause insets are currently visible on screen.
+ *
+ *
+ * @see #isVisible(int)
+ *
+ * @param typeMask The bitmask of {@link InsetType} to set the visibility for.
+ * @param visible Whether to mark the windows as visible or not.
+ *
+ * @return itself
+ * @hide pending unhide
+ */
+ @NonNull
+ public Builder setVisible(@InsetType int typeMask, boolean visible) {
+ for (int i = FIRST; i <= LAST; i = i << 1) {
+ if ((typeMask & i) == 0) {
+ continue;
+ }
+ mTypeVisibilityMap[indexOf(i)] = visible;
+ }
+ return this;
+ }
+
+ /**
* Sets the stable insets in pixels.
*
* <p>The stable inset represents the area of a full-screen window that <b>may</b> be
@@ -916,8 +986,8 @@ public final class WindowInsets {
@NonNull
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
- mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mIsRound,
- mAlwaysConsumeNavBar, mDisplayCutout);
+ mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
+ mIsRound, mAlwaysConsumeNavBar, mDisplayCutout);
}
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index a35be273f3bf..b70832315e2c 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowInsets.Type.ime;
+
import android.annotation.NonNull;
import android.view.WindowInsets.Type.InsetType;
@@ -32,11 +34,11 @@ public interface WindowInsetsController {
* <p>
* Note that if the window currently doesn't have control over a certain type, it will apply the
* change as soon as the window gains control. The app can listen to the event by observing
- * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in
- * {@link WindowInsets}.
+ * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
*
* @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app
* would like to make appear on screen.
+ * @hide
*/
void show(@InsetType int types);
@@ -45,11 +47,11 @@ public interface WindowInsetsController {
* <p>
* Note that if the window currently doesn't have control over a certain type, it will apply the
* change as soon as the window gains control. The app can listen to the event by observing
- * {@link View#onApplyWindowInsets} and checking visibility with "TODO at method" in
- * {@link WindowInsets}.
+ * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
*
* @param types A bitmask of {@link WindowInsets.Type.InsetType} specifying what windows the app
* would like to make disappear.
+ * @hide
*/
void hide(@InsetType int types);
@@ -60,7 +62,50 @@ public interface WindowInsetsController {
* @param types The {@link InsetType}s the application has requested to control.
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
+ * @hide
*/
void controlWindowInsetsAnimation(@InsetType int types,
@NonNull WindowInsetsAnimationControlListener listener);
+
+ /**
+ * Lets the application control the animation for showing the IME in a frame-by-frame manner by
+ * modifying the position of the IME when it's causing insets.
+ *
+ * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
+ * IME are ready to be controlled, among other callbacks.
+ */
+ default void controlInputMethodAnimation(
+ @NonNull WindowInsetsAnimationControlListener listener) {
+ controlWindowInsetsAnimation(ime(), listener);
+ }
+
+ /**
+ * Makes the IME appear on screen.
+ * <p>
+ * Note that if the window currently doesn't have control over the IME, because it doesn't have
+ * focus, it will apply the change as soon as the window gains control. The app can listen to
+ * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
+ * {@link WindowInsets#isVisible}.
+ *
+ * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #hideInputMethod()
+ */
+ default void showInputMethod() {
+ show(ime());
+ }
+
+ /**
+ * Makes the IME disappear on screen.
+ * <p>
+ * Note that if the window currently doesn't have control over IME, because it doesn't have
+ * focus, it will apply the change as soon as the window gains control. The app can listen to
+ * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
+ * {@link WindowInsets#isVisible}.
+ *
+ * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #showInputMethod()
+ */
+ default void hideInputMethod() {
+ hide(ime());
+ }
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 6ed2d801110c..c425e7bd3700 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -34,8 +34,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
-import dalvik.system.CloseGuard;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -148,9 +146,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
@Retention(RetentionPolicy.SOURCE)
@interface FlushReason{}
-
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
private final Object mLock = new Object();
/**
@@ -185,7 +180,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
@VisibleForTesting
public ContentCaptureSession(@NonNull String id) {
mId = Preconditions.checkNotNull(id);
- mCloseGuard.open("destroy");
}
/** @hide */
@@ -251,8 +245,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
}
mDestroyed = true;
- mCloseGuard.close();
-
// TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
// id) and send it to the cache of batched commands
if (VERBOSE) {
@@ -288,18 +280,6 @@ public abstract class ContentCaptureSession implements AutoCloseable {
destroy();
}
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
- destroy();
- } finally {
- super.finalize();
- }
- }
-
/**
* Notifies the Content Capture Service that a node has been added to the view structure.
*
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index d09323d3f8ad..112653aa34e3 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -219,7 +219,7 @@ public interface InputMethod {
@MainThread
default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken) {
+ @NonNull IBinder startInputToken, boolean shouldPreRenderIme) {
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index 77cb4cd28763..4d917a1b1968 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -103,10 +103,9 @@ public final class ActionsSuggestionsHelper {
final String modelName = String.format(
Locale.US, "%s_v%d", localesJoiner.toString(), modelVersion);
final int hash = Objects.hash(
- messages.stream()
- .map(ConversationActions.Message::getText)
- .collect(Collectors.toList()),
- context.getPackageName());
+ messages.stream().mapToInt(ActionsSuggestionsHelper::hashMessage),
+ context.getPackageName(),
+ System.currentTimeMillis());
return SelectionSessionLogger.SignatureParser.createSignature(
SelectionSessionLogger.CLASSIFIER_ID, modelName, hash);
}
@@ -116,7 +115,7 @@ public final class ActionsSuggestionsHelper {
private int mNextUserId = FIRST_NON_LOCAL_USER;
private int encode(Person person) {
- if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) {
+ if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) {
return USER_LOCAL;
}
Integer result = mMapping.get(person);
@@ -128,4 +127,8 @@ public final class ActionsSuggestionsHelper {
return result;
}
}
+
+ private static int hashMessage(ConversationActions.Message message) {
+ return Objects.hash(message.getAuthor(), message.getText(), message.getReferenceTime());
+ }
}
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index f7c1a2640dc5..502181f633b6 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -109,9 +109,9 @@ public final class ConversationActions implements Parcelable {
*
* @see Builder#Builder(Person)
*/
- public static final Person PERSON_USER_LOCAL =
+ public static final Person PERSON_USER_SELF =
new Person.Builder()
- .setKey("text-classifier-conversation-actions-local-user")
+ .setKey("text-classifier-conversation-actions-user-self")
.build();
/**
@@ -123,9 +123,9 @@ public final class ConversationActions implements Parcelable {
*
* @see Builder#Builder(Person)
*/
- public static final Person PERSON_USER_REMOTE =
+ public static final Person PERSON_USER_OTHERS =
new Person.Builder()
- .setKey("text-classifier-conversation-actions-remote-user")
+ .setKey("text-classifier-conversation-actions-user-others")
.build();
@Nullable
@@ -235,10 +235,10 @@ public final class ConversationActions implements Parcelable {
/**
* Constructs a builder.
*
- * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL}
+ * @param author the person that composed the message, use {@link #PERSON_USER_SELF}
* to represent the local user. If it is not possible to identify the
* remote user that the local user is conversing with, use
- * {@link #PERSON_USER_REMOTE} to represent a remote user.
+ * {@link #PERSON_USER_OTHERS} to represent a remote user.
*/
public Builder(@NonNull Person author) {
mAuthor = Preconditions.checkNotNull(author);
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index ed862064be67..10c7adef28fd 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -73,9 +73,16 @@ public final class TextClassificationManager {
/**
* Returns the text classifier that was set via {@link #setTextClassifier(TextClassifier)}.
* If this is null, this method returns a default text classifier (i.e. either the system text
- * classifier if one exists, or a local text classifier running in this app.)
+ * classifier if one exists, or a local text classifier running in this process.)
+ * <p>
+ * Note that if system textclassifier is in use, requests will be sent to a textclassifier
+ * package provided from OEM. If you want to make sure the requests are handled in your own
+ * process, you should consider {@link #getLocalTextClassifier()} instead. However, the local
+ * textclassifier may return inferior results to those returned by the system
+ * textclassifier.
*
* @see #setTextClassifier(TextClassifier)
+ * @see #getLocalTextClassifier()
*/
@NonNull
public TextClassifier getTextClassifier() {
@@ -215,7 +222,13 @@ public final class TextClassificationManager {
return TextClassifier.NO_OP;
}
- private TextClassifier getLocalTextClassifier() {
+ /**
+ * Returns a local textclassifier, which is running in this process.
+ *
+ * @see #getTextClassifier()
+ */
+ @NonNull
+ public TextClassifier getLocalTextClassifier() {
synchronized (mLock) {
if (mLocalTextClassifier == null) {
if (getSettings().isLocalTextClassifierEnabled()) {
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index b84f6f07e414..cd13cc0ec577 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -72,7 +72,7 @@ public final class TextClassifierEvent implements Parcelable {
TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION,
TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION,
TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL,
- TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY})
+ TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED})
public @interface Type {
// For custom event types, use range 1,000,000+.
}
@@ -121,7 +121,7 @@ public final class TextClassifierEvent implements Parcelable {
@Category private final int mEventCategory;
@Type private final int mEventType;
- @Nullable private final String mEntityType;
+ @Nullable private final String[] mEntityTypes;
@Nullable private final TextClassificationContext mEventContext;
@Nullable private final String mResultId;
private final int mEventIndex;
@@ -139,11 +139,12 @@ public final class TextClassifierEvent implements Parcelable {
// Language detection.
@Nullable private final String mLanguage;
+ private final float mScore;
private TextClassifierEvent(
int eventCategory,
int eventType,
- String entityType,
+ String[] entityTypes,
TextClassificationContext eventContext,
String resultId,
int eventIndex,
@@ -154,10 +155,11 @@ public final class TextClassifierEvent implements Parcelable {
int relativeSuggestedWordStartIndex,
int relativeSuggestedWordEndIndex,
int[] actionIndex,
- String language) {
+ String language,
+ float score) {
mEventCategory = eventCategory;
mEventType = eventType;
- mEntityType = entityType;
+ mEntityTypes = entityTypes;
mEventContext = eventContext;
mResultId = resultId;
mEventIndex = eventIndex;
@@ -169,6 +171,7 @@ public final class TextClassifierEvent implements Parcelable {
mRelativeSuggestedWordEndIndex = relativeSuggestedWordEndIndex;
mActionIndices = actionIndex;
mLanguage = language;
+ mScore = score;
}
@Override
@@ -180,7 +183,7 @@ public final class TextClassifierEvent implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mEventCategory);
dest.writeInt(mEventType);
- dest.writeString(mEntityType);
+ dest.writeStringArray(mEntityTypes);
dest.writeParcelable(mEventContext, flags);
dest.writeString(mResultId);
dest.writeInt(mEventIndex);
@@ -192,13 +195,14 @@ public final class TextClassifierEvent implements Parcelable {
dest.writeInt(mRelativeSuggestedWordEndIndex);
dest.writeIntArray(mActionIndices);
dest.writeString(mLanguage);
+ dest.writeFloat(mScore);
}
private static TextClassifierEvent readFromParcel(Parcel in) {
return new TextClassifierEvent(
/* eventCategory= */ in.readInt(),
/* eventType= */ in.readInt(),
- /* entityType= */ in.readString(),
+ /* entityTypes=*/ in.readStringArray(),
/* eventContext= */ in.readParcelable(null),
/* resultId= */ in.readString(),
/* eventIndex= */ in.readInt(),
@@ -209,7 +213,8 @@ public final class TextClassifierEvent implements Parcelable {
/* relativeSuggestedWordStartIndex= */ in.readInt(),
/* relativeSuggestedWordEndIndex= */ in.readInt(),
/* actionIndices= */ in.createIntArray(),
- /* language= */ in.readString());
+ /* language= */ in.readString(),
+ /* score= */ in.readFloat());
}
/**
@@ -229,11 +234,11 @@ public final class TextClassifierEvent implements Parcelable {
}
/**
- * Returns the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+ * Returns an array of entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
*/
- @Nullable
- public String getEntityType() {
- return mEntityType;
+ @NonNull
+ public String[] getEntityTypes() {
+ return mEntityTypes;
}
/**
@@ -327,13 +332,20 @@ public final class TextClassifierEvent implements Parcelable {
}
/**
+ * Returns the score of the suggestion.
+ */
+ public float getScore() {
+ return mScore;
+ }
+
+ /**
* Builder to build a text classifier event.
*/
public static final class Builder {
private final int mEventCategory;
private final int mEventType;
- @Nullable private String mEntityType;
+ private String[] mEntityTypes = new String[0];
@Nullable private TextClassificationContext mEventContext;
@Nullable private String mResultId;
private int mEventIndex;
@@ -345,6 +357,7 @@ public final class TextClassifierEvent implements Parcelable {
private int mRelativeSuggestedWordEndIndex;
private int[] mActionIndices = new int[0];
@Nullable private String mLanguage;
+ private float mScore;
/**
* Creates a builder for building {@link TextClassifierEvent}s.
@@ -358,11 +371,12 @@ public final class TextClassifierEvent implements Parcelable {
}
/**
- * Sets the entity type. e.g. {@link TextClassifier#TYPE_ADDRESS}.
+ * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
*/
@NonNull
- public Builder setEntityType(@Nullable String entityType) {
- mEntityType = entityType;
+ public Builder setEntityTypes(@NonNull String... entityTypes) {
+ mEntityTypes = new String[entityTypes.length];
+ System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
return this;
}
@@ -478,6 +492,15 @@ public final class TextClassifierEvent implements Parcelable {
}
/**
+ * Sets the score of the suggestion.
+ */
+ @NonNull
+ public Builder setScore(float score) {
+ mScore = score;
+ return this;
+ }
+
+ /**
* Builds and returns a text classifier event.
*/
@NonNull
@@ -486,7 +509,7 @@ public final class TextClassifierEvent implements Parcelable {
return new TextClassifierEvent(
mEventCategory,
mEventType,
- mEntityType,
+ mEntityTypes,
mEventContext,
mResultId,
mEventIndex,
@@ -497,7 +520,8 @@ public final class TextClassifierEvent implements Parcelable {
mRelativeSuggestedWordStartIndex,
mRelativeSuggestedWordEndIndex,
mActionIndices,
- mLanguage);
+ mLanguage,
+ mScore);
}
// TODO: Add build(boolean validate).
}
@@ -507,7 +531,7 @@ public final class TextClassifierEvent implements Parcelable {
StringBuilder out = new StringBuilder(128);
out.append("TextClassifierEvent{");
out.append("mEventCategory=").append(mEventCategory);
- out.append(", mEventType=").append(mEventType);
+ out.append(", mEventTypes=").append(Arrays.toString(mEntityTypes));
out.append(", mEventContext=").append(mEventContext);
out.append(", mResultId=").append(mResultId);
out.append(", mEventIndex=").append(mEventIndex);
@@ -519,6 +543,7 @@ public final class TextClassifierEvent implements Parcelable {
out.append(", mRelativeSuggestedWordEndIndex=").append(mRelativeSuggestedWordEndIndex);
out.append(", mActionIndices=").append(Arrays.toString(mActionIndices));
out.append(", mLanguage=").append(mLanguage);
+ out.append(", mScore=").append(mScore);
out.append("}");
return out.toString();
}
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
index 439e594cc8fe..5563dfc2eee5 100644
--- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
+++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
@@ -15,12 +15,15 @@
*/
package android.view.textclassifier;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_SESSION_ID;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SESSION_ID;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_VERSION;
import android.metrics.LogMaker;
@@ -60,16 +63,30 @@ public final class TextClassifierEventTronLogger {
return;
}
final LogMaker log = new LogMaker(category)
- .setType(getLogType(event))
- .addTaggedData(FIELD_SELECTION_SESSION_ID, event.getResultId())
+ .setSubtype(getLogType(event))
+ .addTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID, event.getResultId())
.addTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME, event.getEventTime())
.addTaggedData(FIELD_TEXTCLASSIFIER_MODEL,
SelectionSessionLogger.SignatureParser.getModelName(event.getResultId()))
- .addTaggedData(FIELD_SELECTION_ENTITY_TYPE, event.getEntityType());
+ .addTaggedData(FIELD_TEXT_CLASSIFIER_SCORE, event.getScore());
+
+ String[] entityTypes = event.getEntityTypes();
+ // TRON does not support a field of list type, and thus workaround by store them
+ // in three separate fields. This is no longer an issue once we have moved to Westworld.
+ if (entityTypes.length >= 1) {
+ log.addTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE, entityTypes[0]);
+ }
+ if (entityTypes.length >= 2) {
+ log.addTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE, entityTypes[1]);
+ }
+ if (entityTypes.length >= 3) {
+ log.addTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE, entityTypes[2]);
+ }
TextClassificationContext eventContext = event.getEventContext();
if (eventContext != null) {
- log.addTaggedData(FIELD_SELECTION_WIDGET_TYPE, eventContext.getWidgetType());
- log.addTaggedData(FIELD_SELECTION_WIDGET_VERSION, eventContext.getWidgetVersion());
+ log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE, eventContext.getWidgetType());
+ log.addTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION,
+ eventContext.getWidgetVersion());
log.setPackageName(eventContext.getPackageName());
}
mMetricsLogger.write(log);
@@ -94,6 +111,8 @@ public final class TextClassifierEventTronLogger {
return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN;
case TextClassifierEvent.TYPE_MANUAL_REPLY:
return MetricsEvent.ACTION_TEXT_CLASSIFIER_MANUAL_REPLY;
+ case TextClassifierEvent.TYPE_ACTIONS_GENERATED:
+ return MetricsEvent.ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED;
default:
return MetricsEvent.VIEW_UNKNOWN;
}
@@ -127,14 +146,22 @@ public final class TextClassifierEventTronLogger {
if (!Log.ENABLE_FULL_LOGGING) {
return;
}
- final String id = String.valueOf(log.getTaggedData(FIELD_SELECTION_SESSION_ID));
+ final String id = String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SESSION_ID));
final String categoryName = toCategoryName(log.getCategory());
- final String eventName = toEventName(log.getType());
- final String widgetType = String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_TYPE));
+ final String eventName = toEventName(log.getSubtype());
+ final String widgetType =
+ String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE));
final String widgetVersion =
- String.valueOf(log.getTaggedData(FIELD_SELECTION_WIDGET_VERSION));
+ String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_VERSION));
final String model = String.valueOf(log.getTaggedData(FIELD_TEXTCLASSIFIER_MODEL));
- final String entityType = String.valueOf(log.getTaggedData(FIELD_SELECTION_ENTITY_TYPE));
+ final String firstEntityType =
+ String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE));
+ final String secondEntityType =
+ String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE));
+ final String thirdEntityType =
+ String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE));
+ final String score =
+ String.valueOf(log.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE));
StringBuilder builder = new StringBuilder();
builder.append("writeEvent: ");
@@ -144,7 +171,10 @@ public final class TextClassifierEventTronLogger {
builder.append(", widgetType=").append(widgetType);
builder.append(", widgetVersion=").append(widgetVersion);
builder.append(", model=").append(model);
- builder.append(", entityType=").append(entityType);
+ builder.append(", firstEntityType=").append(firstEntityType);
+ builder.append(", secondEntityType=").append(secondEntityType);
+ builder.append(", thirdEntityType=").append(thirdEntityType);
+ builder.append(", score=").append(score);
Log.v(TAG, builder.toString());
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9ab963e372b7..a5b7c621be38 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -78,6 +78,9 @@ import java.util.concurrent.TimeUnit;
*/
public final class TextClassifierImpl implements TextClassifier {
+ /** @hide */
+ public static final String ACTIONS_INTENTS = "actions-intents";
+
private static final String LOG_TAG = DEFAULT_LOG_TAG;
private static final boolean DEBUG = false;
@@ -567,6 +570,7 @@ public final class TextClassifierImpl implements TextClassifier {
// TODO: Make this configurable.
final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
boolean isPrimaryAction = true;
+ final ArrayList<Intent> sourceIntents = new ArrayList<>();
for (LabeledIntent labeledIntent : IntentFactory.create(
mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
referenceTime, highestScoringResult)) {
@@ -586,9 +590,15 @@ public final class TextClassifierImpl implements TextClassifier {
isPrimaryAction = false;
}
builder.addAction(action);
+ sourceIntents.add(labeledIntent.getIntent());
}
- return builder.setId(createId(text, start, end)).build();
+ final Bundle extras = new Bundle();
+ extras.putParcelableArrayList(ACTIONS_INTENTS, sourceIntents);
+
+ return builder.setId(createId(text, start, end))
+ .setExtras(extras)
+ .build();
}
private boolean isForeignText(String text, float threshold) {
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 3a1c4576ab3e..de1f3df61462 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -150,6 +150,7 @@ public class WebViewZygote {
}
try {
+ String abi = sPackage.applicationInfo.primaryCpuAbi;
sZygote = Process.ZYGOTE_PROCESS.startChildZygote(
"com.android.internal.os.WebViewZygoteInit",
"webview_zygote",
@@ -158,39 +159,40 @@ public class WebViewZygote {
null, // gids
0, // runtimeFlags
"webview_zygote", // seInfo
- sPackage.applicationInfo.primaryCpuAbi, // abi
+ abi, // abi
TextUtils.join(",", Build.SUPPORTED_ABIS),
null, // instructionSet
Process.FIRST_ISOLATED_UID,
Process.LAST_ISOLATED_UID);
-
- // All the work below is usually done by LoadedApk, but the zygote can't talk to
- // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
- // doesn't have an ActivityThread and can't use Binder.
- // Instead, figure out the paths here, in the system server where we have access to
- // the package manager. Reuse the logic from LoadedApk to determine the correct
- // paths and pass them to the zygote as strings.
- final List<String> zipPaths = new ArrayList<>(10);
- final List<String> libPaths = new ArrayList<>(10);
- LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
- final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
- final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
- TextUtils.join(File.pathSeparator, zipPaths);
-
- String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
-
- // In the case where the ApplicationInfo has been modified by the stub WebView,
- // we need to use the original ApplicationInfo to determine what the original classpath
- // would have been to use as a cache key.
- LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
- final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
- TextUtils.join(File.pathSeparator, zipPaths);
-
ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
- Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
- sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
- Build.SUPPORTED_ABIS[0]);
+ if (sPackageOriginalAppInfo.sourceDir.equals(sPackage.applicationInfo.sourceDir)) {
+ // No stub WebView is involved here, so we can preload the package the "clean" way
+ // using the ApplicationInfo.
+ sZygote.preloadApp(sPackage.applicationInfo, abi);
+ } else {
+ // Legacy path to support the stub WebView.
+ // Reuse the logic from LoadedApk to determine the correct paths and pass them to
+ // the zygote as strings.
+ final List<String> zipPaths = new ArrayList<>(10);
+ final List<String> libPaths = new ArrayList<>(10);
+ LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
+ final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
+ final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
+ TextUtils.join(File.pathSeparator, zipPaths);
+
+ String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
+
+ // Use the original ApplicationInfo to determine what the original classpath would
+ // have been to use as a cache key.
+ LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
+ final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
+ TextUtils.join(File.pathSeparator, zipPaths);
+
+ Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
+ sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
+ Build.SUPPORTED_ABIS[0]);
+ }
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to webview zygote", e);
stopZygoteLocked();
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 7c371cb18878..d0102a72e703 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -17,13 +17,10 @@
package com.android.internal.app;
import android.annotation.NonNull;
-import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -31,8 +28,6 @@ import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
-import com.android.internal.R;
-
import java.util.ArrayList;
import java.util.Set;
@@ -44,14 +39,6 @@ public class AssistUtils {
private static final String TAG = "AssistUtils";
- /**
- * Sentinel value for "no default assistant specified."
- *
- * Empty string is already used to represent an explicit setting of No Assistant. null cannot
- * be used because we can't represent a null value in XML.
- */
- private static final String UNSET = "#+UNSET";
-
private final Context mContext;
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
@@ -186,37 +173,9 @@ public class AssistUtils {
Settings.Secure.ASSISTANT, userId);
if (setting != null) {
return ComponentName.unflattenFromString(setting);
- }
-
- final String defaultSetting = mContext.getResources().getString(
- R.string.config_defaultAssistantComponentName);
- if (defaultSetting != null && !defaultSetting.equals(UNSET)) {
- return ComponentName.unflattenFromString(defaultSetting);
- }
-
- // Fallback to keep backward compatible behavior when there is no user setting.
- if (activeServiceSupportsAssistGesture()) {
- return getActiveServiceComponentName();
- }
-
- if (UNSET.equals(defaultSetting)) {
+ } else {
return null;
}
-
- final SearchManager searchManager =
- (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
- if (searchManager == null) {
- return null;
- }
- final Intent intent = searchManager.getAssistIntent(false);
- PackageManager pm = mContext.getPackageManager();
- ResolveInfo info = pm.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY,
- userId);
- if (info != null) {
- return new ComponentName(info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- }
- return null;
}
public static boolean isPreinstalledAssistant(Context context, ComponentName assistant) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 30137e3893ff..803462d59fad 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,10 @@ package com.android.internal.app;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -98,6 +102,20 @@ public class ChooserActivity extends ResolverActivity {
private static final boolean DEBUG = false;
+
+ /**
+ * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+ * {@link AppPredictionManager} will be queried for direct share targets.
+ */
+ // TODO(b/123089490): Replace with system flag
+ private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
+ // TODO(b/123088566) Share these in a better way.
+ private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+ private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
+ public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+ private AppPredictor mAppPredictor;
+ private AppPredictor.Callback mAppPredictorCallback;
+
/**
* If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
* binding to every ChooserTargetService implementation.
@@ -309,6 +327,35 @@ public class ChooserActivity extends ResolverActivity {
mChooserShownTime = System.currentTimeMillis();
final long systemCost = mChooserShownTime - intentReceivedTime;
MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost);
+
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ final IntentFilter filter = getTargetIntentFilter();
+ Bundle extras = new Bundle();
+ extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
+ AppPredictionManager appPredictionManager =
+ getSystemService(AppPredictionManager.class);
+ mAppPredictor = appPredictionManager.createAppPredictionSession(
+ new AppPredictionContext.Builder(this)
+ .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
+ .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+ .setExtras(extras)
+ .build());
+ mAppPredictorCallback = resultList -> {
+ final List<DisplayResolveInfo> driList =
+ getDisplayResolveInfos(mChooserListAdapter);
+ final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
+ new ArrayList<>();
+ for (AppTarget appTarget : resultList) {
+ shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
+ appTarget.getShortcutInfo(),
+ new ComponentName(
+ appTarget.getPackageName(), appTarget.getClassName())));
+ }
+ sendShareShortcutInfoList(shareShortcutInfos, driList);
+ };
+ mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
+ }
+
if (DEBUG) {
Log.d(TAG, "System Time Cost is " + systemCost);
}
@@ -339,6 +386,10 @@ public class ChooserActivity extends ResolverActivity {
}
unbindRemainingServices();
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+ mAppPredictor.destroy();
+ }
}
@Override
@@ -513,6 +564,7 @@ public class ChooserActivity extends ResolverActivity {
void queryTargetServices(ChooserListAdapter adapter) {
final PackageManager pm = getPackageManager();
+ ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
int targetsToQuery = 0;
for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -522,6 +574,11 @@ public class ChooserActivity extends ResolverActivity {
continue;
}
final ActivityInfo ai = dri.getResolveInfo().activityInfo;
+ if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+ && sm.hasShareTargets(ai.packageName)) {
+ // Share targets will be queried from ShortcutManager
+ continue;
+ }
final Bundle md = ai.metaData;
final String serviceName = md != null ? convertServiceName(ai.packageName,
md.getString(ChooserTargetService.META_DATA_NAME)) : null;
@@ -600,15 +657,10 @@ public class ChooserActivity extends ResolverActivity {
}
}
- private void queryDirectShareTargets(ChooserListAdapter adapter) {
- final IntentFilter filter = getTargetIntentFilter();
- if (filter == null) {
- return;
- }
-
+ private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
// Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
// and use the old code path. This Ugliness should go away when Sharesheet is refactored.
- final List<DisplayResolveInfo> driList = new ArrayList<>();
+ List<DisplayResolveInfo> driList = new ArrayList<>();
int targetsToQuery = 0;
for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -628,40 +680,57 @@ public class ChooserActivity extends ResolverActivity {
break;
}
}
+ return driList;
+ }
+
+ private void queryDirectShareTargets(ChooserListAdapter adapter) {
+ if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ mAppPredictor.requestPredictionUpdate();
+ return;
+ }
+ final IntentFilter filter = getTargetIntentFilter();
+ if (filter == null) {
+ return;
+ }
+ final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
AsyncTask.execute(() -> {
ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
+ sendShareShortcutInfoList(resultList, driList);
+ });
+ }
- // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
- // for direct share targets. After ShareSheet is refactored we should use the
- // ShareShortcutInfos directly.
- boolean resultMessageSent = false;
- for (int i = 0; i < driList.size(); i++) {
- List<ChooserTarget> chooserTargets = new ArrayList<>();
- for (int j = 0; j < resultList.size(); j++) {
- if (driList.get(i).getResolvedComponentName().equals(
+ private void sendShareShortcutInfoList(
+ List<ShortcutManager.ShareShortcutInfo> resultList,
+ List<DisplayResolveInfo> driList) {
+ // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
+ // for direct share targets. After ShareSheet is refactored we should use the
+ // ShareShortcutInfos directly.
+ boolean resultMessageSent = false;
+ for (int i = 0; i < driList.size(); i++) {
+ List<ChooserTarget> chooserTargets = new ArrayList<>();
+ for (int j = 0; j < resultList.size(); j++) {
+ if (driList.get(i).getResolvedComponentName().equals(
resultList.get(j).getTargetComponent())) {
- chooserTargets.add(convertToChooserTarget(resultList.get(j)));
- }
+ chooserTargets.add(convertToChooserTarget(resultList.get(j)));
}
- if (chooserTargets.isEmpty()) {
- continue;
- }
-
- final Message msg = Message.obtain();
- msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
- msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
- mChooserHandler.sendMessage(msg);
- resultMessageSent = true;
}
-
- if (resultMessageSent) {
- final Message msg = Message.obtain();
- msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
- mChooserHandler.sendMessage(msg);
+ if (chooserTargets.isEmpty()) {
+ continue;
}
- });
+ final Message msg = Message.obtain();
+ msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
+ msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+ mChooserHandler.sendMessage(msg);
+ resultMessageSent = true;
+ }
+
+ if (resultMessageSent) {
+ final Message msg = Message.obtain();
+ msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+ mChooserHandler.sendMessage(msg);
+ }
}
private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
@@ -718,6 +787,7 @@ public class ChooserActivity extends ResolverActivity {
// Do nothing. We'll send the voice stuff ourselves.
}
+ // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor
void updateModelAndChooserCounts(TargetInfo info) {
if (info != null) {
final ResolveInfo ri = info.getResolveInfo();
diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
index c093fe512186..2ac0e4de58ac 100644
--- a/core/java/com/android/internal/app/ColorDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -16,28 +16,20 @@
package com.android.internal.app;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
-import android.metrics.LogMaker;
+import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.ColorDisplayManager.AutoMode;
+import android.hardware.display.ColorDisplayManager.ColorMode;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemProperties;
import android.provider.Settings.Secure;
-import android.provider.Settings.System;
import android.util.Slog;
-import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.time.LocalDateTime;
import java.time.LocalTime;
/**
@@ -51,67 +43,12 @@ public final class ColorDisplayController {
private static final String TAG = "ColorDisplayController";
private static final boolean DEBUG = false;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT })
- public @interface AutoMode {}
-
- /**
- * Auto mode value to prevent Night display from being automatically activated. It can still
- * be activated manually via {@link #setActivated(boolean)}.
- *
- * @see #setAutoMode(int)
- */
- public static final int AUTO_MODE_DISABLED = 0;
- /**
- * Auto mode value to automatically activate Night display at a specific start and end time.
- *
- * @see #setAutoMode(int)
- * @see #setCustomStartTime(LocalTime)
- * @see #setCustomEndTime(LocalTime)
- */
- public static final int AUTO_MODE_CUSTOM = 1;
- /**
- * Auto mode value to automatically activate Night display from sunset to sunrise.
- *
- * @see #setAutoMode(int)
- */
- public static final int AUTO_MODE_TWILIGHT = 2;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC })
- public @interface ColorMode {}
-
- /**
- * Color mode with natural colors.
- *
- * @see #setColorMode(int)
- */
- public static final int COLOR_MODE_NATURAL = 0;
- /**
- * Color mode with boosted colors.
- *
- * @see #setColorMode(int)
- */
- public static final int COLOR_MODE_BOOSTED = 1;
- /**
- * Color mode with saturated colors.
- *
- * @see #setColorMode(int)
- */
- public static final int COLOR_MODE_SATURATED = 2;
- /**
- * Color mode with automatic colors.
- *
- * @see #setColorMode(int)
- */
- public static final int COLOR_MODE_AUTOMATIC = 3;
-
private final Context mContext;
private final int mUserId;
+ private final ColorDisplayManager mColorDisplayManager;
private ContentObserver mContentObserver;
private Callback mCallback;
- private MetricsLogger mMetricsLogger;
public ColorDisplayController(@NonNull Context context) {
this(context, ActivityManager.getCurrentUser());
@@ -120,14 +57,14 @@ public final class ColorDisplayController {
public ColorDisplayController(@NonNull Context context, int userId) {
mContext = context.getApplicationContext();
mUserId = userId;
+ mColorDisplayManager = mContext.getSystemService(ColorDisplayManager.class);
}
/**
* Returns {@code true} when Night display is activated (the display is tinted red).
*/
public boolean isActivated() {
- return Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1;
+ return mColorDisplayManager.isNightDisplayActivated();
}
/**
@@ -137,40 +74,16 @@ public final class ColorDisplayController {
* @return {@code true} if the activated value was set successfully
*/
public boolean setActivated(boolean activated) {
- if (isActivated() != activated) {
- Secure.putStringForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
- LocalDateTime.now().toString(),
- mUserId);
- }
- return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId);
+ return mColorDisplayManager.setNightDisplayActivated(activated);
}
/**
* Returns the current auto mode value controlling when Night display will be automatically
- * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or
- * {@link #AUTO_MODE_TWILIGHT}.
+ * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link
+ * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}.
*/
public @AutoMode int getAutoMode() {
- int autoMode = Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId);
- if (autoMode == -1) {
- if (DEBUG) {
- Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE);
- }
- autoMode = mContext.getResources().getInteger(
- R.integer.config_defaultNightDisplayAutoMode);
- }
-
- if (autoMode != AUTO_MODE_DISABLED
- && autoMode != AUTO_MODE_CUSTOM
- && autoMode != AUTO_MODE_TWILIGHT) {
- Slog.e(TAG, "Invalid autoMode: " + autoMode);
- autoMode = AUTO_MODE_DISABLED;
- }
-
- return autoMode;
+ return mColorDisplayManager.getNightDisplayAutoMode();
}
/**
@@ -178,138 +91,64 @@ public final class ColorDisplayController {
* never been set.
*/
public int getAutoModeRaw() {
- return Secure.getIntForUser(mContext.getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,
- -1, mUserId);
+ return mColorDisplayManager.getNightDisplayAutoModeRaw();
}
/**
* Sets the current auto mode value controlling when Night display will be automatically
- * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or
- * {@link #AUTO_MODE_TWILIGHT}.
+ * activated. One of {@link ColorDisplayManager#AUTO_MODE_DISABLED}, {@link
+ * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME} or {@link ColorDisplayManager#AUTO_MODE_TWILIGHT}.
*
* @param autoMode the new auto mode to use
* @return {@code true} if new auto mode was set successfully
*/
public boolean setAutoMode(@AutoMode int autoMode) {
- if (autoMode != AUTO_MODE_DISABLED
- && autoMode != AUTO_MODE_CUSTOM
- && autoMode != AUTO_MODE_TWILIGHT) {
- throw new IllegalArgumentException("Invalid autoMode: " + autoMode);
- }
-
- if (getAutoMode() != autoMode) {
- Secure.putStringForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
- null,
- mUserId);
- getMetricsLogger().write(new LogMaker(
- MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CHANGED)
- .setType(MetricsEvent.TYPE_ACTION)
- .setSubtype(autoMode));
- }
-
- return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId);
+ return mColorDisplayManager.setNightDisplayAutoMode(autoMode);
}
/**
- * Returns the local time when Night display will be automatically activated when using
- * {@link #AUTO_MODE_CUSTOM}.
+ * Returns the local time when Night display will be automatically activated when using {@link
+ * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
*/
public @NonNull LocalTime getCustomStartTime() {
- int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId);
- if (startTimeValue == -1) {
- if (DEBUG) {
- Slog.d(TAG, "Using default value for setting: "
- + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME);
- }
- startTimeValue = mContext.getResources().getInteger(
- R.integer.config_defaultNightDisplayCustomStartTime);
- }
-
- return LocalTime.ofSecondOfDay(startTimeValue / 1000);
+ return mColorDisplayManager.getNightDisplayCustomStartTime();
}
/**
- * Sets the local time when Night display will be automatically activated when using
- * {@link #AUTO_MODE_CUSTOM}.
+ * Sets the local time when Night display will be automatically activated when using {@link
+ * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
*
* @param startTime the local time to automatically activate Night display
* @return {@code true} if the new custom start time was set successfully
*/
public boolean setCustomStartTime(@NonNull LocalTime startTime) {
- if (startTime == null) {
- throw new IllegalArgumentException("startTime cannot be null");
- }
- getMetricsLogger().write(new LogMaker(
- MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
- .setType(MetricsEvent.TYPE_ACTION)
- .setSubtype(0));
- return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId);
+ return mColorDisplayManager.setNightDisplayCustomStartTime(startTime);
}
/**
- * Returns the local time when Night display will be automatically deactivated when using
- * {@link #AUTO_MODE_CUSTOM}.
+ * Returns the local time when Night display will be automatically deactivated when using {@link
+ * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
*/
public @NonNull LocalTime getCustomEndTime() {
- int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId);
- if (endTimeValue == -1) {
- if (DEBUG) {
- Slog.d(TAG, "Using default value for setting: "
- + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME);
- }
- endTimeValue = mContext.getResources().getInteger(
- R.integer.config_defaultNightDisplayCustomEndTime);
- }
-
- return LocalTime.ofSecondOfDay(endTimeValue / 1000);
+ return mColorDisplayManager.getNightDisplayCustomEndTime();
}
/**
- * Sets the local time when Night display will be automatically deactivated when using
- * {@link #AUTO_MODE_CUSTOM}.
+ * Sets the local time when Night display will be automatically deactivated when using {@link
+ * ColorDisplayManager#AUTO_MODE_CUSTOM_TIME}.
*
* @param endTime the local time to automatically deactivate Night display
* @return {@code true} if the new custom end time was set successfully
*/
public boolean setCustomEndTime(@NonNull LocalTime endTime) {
- if (endTime == null) {
- throw new IllegalArgumentException("endTime cannot be null");
- }
- getMetricsLogger().write(new LogMaker(
- MetricsEvent.ACTION_NIGHT_DISPLAY_AUTO_MODE_CUSTOM_TIME_CHANGED)
- .setType(MetricsEvent.TYPE_ACTION)
- .setSubtype(1));
- return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId);
+ return mColorDisplayManager.setNightDisplayCustomEndTime(endTime);
}
/**
* Returns the color temperature (in Kelvin) to tint the display when activated.
*/
public int getColorTemperature() {
- int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId);
- if (colorTemperature == -1) {
- if (DEBUG) {
- Slog.d(TAG, "Using default value for setting: "
- + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE);
- }
- colorTemperature = getDefaultColorTemperature();
- }
- final int minimumTemperature = getMinimumColorTemperature();
- final int maximumTemperature = getMaximumColorTemperature();
- if (colorTemperature < minimumTemperature) {
- colorTemperature = minimumTemperature;
- } else if (colorTemperature > maximumTemperature) {
- colorTemperature = maximumTemperature;
- }
-
- return colorTemperature;
+ return mColorDisplayManager.getNightDisplayColorTemperature();
}
/**
@@ -319,79 +158,14 @@ public final class ColorDisplayController {
* @return {@code true} if new temperature was set successfully.
*/
public boolean setColorTemperature(int colorTemperature) {
- return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId);
- }
-
- /**
- * Get the current color mode from system properties, or return -1.
- *
- * See com.android.server.display.DisplayTransformManager.
- */
- private @ColorMode int getCurrentColorModeFromSystemProperties() {
- final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
- if (displayColorSetting == 0) {
- return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
- ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
- } else if (displayColorSetting == 1) {
- return COLOR_MODE_SATURATED;
- } else if (displayColorSetting == 2) {
- return COLOR_MODE_AUTOMATIC;
- } else {
- return -1;
- }
- }
-
- private boolean isColorModeAvailable(@ColorMode int colorMode) {
- final int[] availableColorModes = mContext.getResources().getIntArray(
- R.array.config_availableColorModes);
- if (availableColorModes != null) {
- for (int mode : availableColorModes) {
- if (mode == colorMode) {
- return true;
- }
- }
- }
- return false;
+ return mColorDisplayManager.setNightDisplayColorTemperature(colorTemperature);
}
/**
* Get the current color mode.
*/
public int getColorMode() {
- if (getAccessibilityTransformActivated()) {
- if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
- return COLOR_MODE_SATURATED;
- } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
- return COLOR_MODE_AUTOMATIC;
- }
- }
-
- int colorMode = System.getIntForUser(mContext.getContentResolver(),
- System.DISPLAY_COLOR_MODE, -1, mUserId);
- if (colorMode == -1) {
- // There might be a system property controlling color mode that we need to respect; if
- // not, this will set a suitable default.
- colorMode = getCurrentColorModeFromSystemProperties();
- }
-
- // This happens when a color mode is no longer available (e.g., after system update or B&R)
- // or the device does not support any color mode.
- if (!isColorModeAvailable(colorMode)) {
- if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
- colorMode = COLOR_MODE_NATURAL;
- } else if (colorMode == COLOR_MODE_SATURATED
- && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
- colorMode = COLOR_MODE_AUTOMATIC;
- } else if (colorMode == COLOR_MODE_AUTOMATIC
- && isColorModeAvailable(COLOR_MODE_SATURATED)) {
- colorMode = COLOR_MODE_SATURATED;
- } else {
- colorMode = -1;
- }
- }
-
- return colorMode;
+ return mColorDisplayManager.getColorMode();
}
/**
@@ -400,47 +174,21 @@ public final class ColorDisplayController {
* @param colorMode the color mode
*/
public void setColorMode(@ColorMode int colorMode) {
- if (!isColorModeAvailable(colorMode)) {
- throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
- }
- System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
- mUserId);
+ mColorDisplayManager.setColorMode(colorMode);
}
/**
* Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
*/
public int getMinimumColorTemperature() {
- return mContext.getResources().getInteger(
- R.integer.config_nightDisplayColorTemperatureMin);
+ return ColorDisplayManager.getMinimumColorTemperature(mContext);
}
/**
* Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated.
*/
public int getMaximumColorTemperature() {
- return mContext.getResources().getInteger(
- R.integer.config_nightDisplayColorTemperatureMax);
- }
-
- /**
- * Returns the default color temperature (in Kelvin) to tint the display when activated.
- */
- public int getDefaultColorTemperature() {
- return mContext.getResources().getInteger(
- R.integer.config_nightDisplayColorTemperatureDefault);
- }
-
- /**
- * Returns true if any Accessibility color transforms are enabled.
- */
- public boolean getAccessibilityTransformActivated() {
- final ContentResolver cr = mContext.getContentResolver();
- return
- Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
- 0, mUserId) == 1
- || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
- 0, mUserId) == 1;
+ return ColorDisplayManager.getMaximumColorTemperature(mContext);
}
private void onSettingChanged(@NonNull String setting) {
@@ -465,13 +213,6 @@ public final class ColorDisplayController {
case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
mCallback.onColorTemperatureChanged(getColorTemperature());
break;
- case System.DISPLAY_COLOR_MODE:
- mCallback.onDisplayColorModeChanged(getColorMode());
- break;
- case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED:
- case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
- mCallback.onAccessibilityTransformChanged(getAccessibilityTransformActivated());
- break;
}
}
}
@@ -514,25 +255,10 @@ public final class ColorDisplayController {
false /* notifyForDescendants */, mContentObserver, mUserId);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
false /* notifyForDescendants */, mContentObserver, mUserId);
- cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
- false /* notifyForDecendants */, mContentObserver, mUserId);
- cr.registerContentObserver(
- Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
- false /* notifyForDecendants */, mContentObserver, mUserId);
- cr.registerContentObserver(
- Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
- false /* notifyForDecendants */, mContentObserver, mUserId);
}
}
}
- private MetricsLogger getMetricsLogger() {
- if (mMetricsLogger == null) {
- mMetricsLogger = new MetricsLogger();
- }
- return mMetricsLogger;
- }
-
/**
* Callback invoked whenever the Night display settings are changed.
*/
@@ -568,19 +294,5 @@ public final class ColorDisplayController {
* @param colorTemperature the color temperature to tint the screen
*/
default void onColorTemperatureChanged(int colorTemperature) {}
-
- /**
- * Callback invoked when the color mode changes.
- *
- * @param displayColorMode the color mode
- */
- default void onDisplayColorModeChanged(int displayColorMode) {}
-
- /**
- * Callback invoked when Accessibility color transforms change.
- *
- * @param state the state Accessibility color transforms (true of active)
- */
- default void onAccessibilityTransformChanged(boolean state) {}
}
}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 514ff76372a9..d7514d1fe26c 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -154,4 +154,7 @@ interface IBatteryStats {
oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info);
oneway void noteModemControllerActivity(in ModemActivityInfo info);
oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info);
+
+ /** {@hide} */
+ boolean setChargingStateUpdateDelayMillis(int delay);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 7600dc9be447..8978496073e5 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -100,6 +100,7 @@ public final class InputMethodPrivilegedOperations {
* @param backDisposition disposition flags
* @see android.inputmethodservice.InputMethodService#IME_ACTIVE
* @see android.inputmethodservice.InputMethodService#IME_VISIBLE
+ * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
* @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
* @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
*/
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 9bacf9b6c2b9..f8483461c2d2 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -64,6 +64,9 @@ public class NetworkStatsFactory {
private boolean mUseBpfStats;
+ // A persistent Snapshot since device start for eBPF stats
+ private final NetworkStats mPersistSnapshot;
+
// TODO: only do adjustments in NetworkStatsService and remove this.
/**
* (Stacked interface) -> (base interface) association for all connected ifaces since boot.
@@ -135,6 +138,7 @@ public class NetworkStatsFactory {
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
mUseBpfStats = useBpfStats;
+ mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -268,6 +272,7 @@ public class NetworkStatsFactory {
return stats;
}
+ // TODO: delete the lastStats parameter
private NetworkStats readNetworkStatsDetailInternal(int limitUid, String[] limitIfaces,
int limitTag, NetworkStats lastStats) throws IOException {
if (USE_NATIVE_PARSING) {
@@ -278,16 +283,28 @@ public class NetworkStatsFactory {
} else {
stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
- if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
- limitIfaces, limitTag, mUseBpfStats) != 0) {
- throw new IOException("Failed to parse network stats");
- }
- if (SANITY_CHECK_NATIVE) {
- final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
- limitIfaces, limitTag);
- assertEquals(javaStats, stats);
+ if (mUseBpfStats) {
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+ null, TAG_ALL, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
+ mPersistSnapshot.combineAllValues(stats);
+ NetworkStats result = mPersistSnapshot.clone();
+ result.filter(limitUid, limitIfaces, limitTag);
+ return result;
+ } else {
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
+ limitIfaces, limitTag, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ if (SANITY_CHECK_NATIVE) {
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
+ limitIfaces, limitTag);
+ assertEquals(javaStats, stats);
+ }
+ return stats;
}
- return stats;
} else {
return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
}
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index fd03b3f16348..da8605e645b4 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -28,6 +28,7 @@ import android.content.res.Resources;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.Network;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.os.Parcel;
import android.os.Parcelable;
@@ -104,6 +105,7 @@ public class VpnConfig implements Parcelable {
public boolean allowIPv4;
public boolean allowIPv6;
public Network[] underlyingNetworks;
+ public ProxyInfo proxyInfo;
public void updateAllowedFamilies(InetAddress address) {
if (address instanceof Inet4Address) {
@@ -164,6 +166,7 @@ public class VpnConfig implements Parcelable {
out.writeInt(allowIPv4 ? 1 : 0);
out.writeInt(allowIPv6 ? 1 : 0);
out.writeTypedArray(underlyingNetworks, flags);
+ out.writeParcelable(proxyInfo, flags);
}
public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -189,6 +192,7 @@ public class VpnConfig implements Parcelable {
config.allowIPv4 = in.readInt() != 0;
config.allowIPv6 = in.readInt() != 0;
config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
+ config.proxyInfo = in.readParcelable(null);
return config;
}
@@ -220,6 +224,7 @@ public class VpnConfig implements Parcelable {
.append(", allowIPv4=").append(allowIPv4)
.append(", allowIPv6=").append(allowIPv6)
.append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks))
+ .append(", proxyInfo=").append(proxyInfo.toString())
.append("}")
.toString();
}
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
index a676dacb0c49..b1a412871bd2 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -32,11 +32,11 @@ public class VpnInfo implements Parcelable {
@Override
public String toString() {
- return "VpnInfo{" +
- "ownerUid=" + ownerUid +
- ", vpnIface='" + vpnIface + '\'' +
- ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' +
- '}';
+ return "VpnInfo{"
+ + "ownerUid=" + ownerUid
+ + ", vpnIface='" + vpnIface + '\''
+ + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
+ + '}';
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 534361e13c7d..c6afee24cfb5 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -13395,11 +13395,22 @@ public class BatteryStatsImpl extends BatteryStats {
mResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS),
false /* notifyForDescendants */, this);
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY),
+ false /* notifyForDescendants */, this);
updateConstants();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
+ if (uri.equals(
+ Settings.Global.getUriFor(
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY))) {
+ synchronized (BatteryStatsImpl.this) {
+ updateBatteryChargedDelayMsLocked();
+ }
+ return;
+ }
updateConstants();
}
@@ -13443,12 +13454,21 @@ public class BatteryStatsImpl extends BatteryStats {
DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
- BATTERY_CHARGED_DELAY_MS = mParser.getInt(
- KEY_BATTERY_CHARGED_DELAY_MS,
- DEFAULT_BATTERY_CHARGED_DELAY_MS);
+ updateBatteryChargedDelayMsLocked();
}
}
+ private void updateBatteryChargedDelayMsLocked() {
+ // a negative value indicates that we should ignore this override
+ final int delay = Settings.Global.getInt(mResolver,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+ -1);
+
+ BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
+ KEY_BATTERY_CHARGED_DELAY_MS,
+ DEFAULT_BATTERY_CHARGED_DELAY_MS);
+ }
+
private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
if (isEnabled && !wasEnabled) {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 64543522893e..2c272dea073b 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -49,7 +49,7 @@ import java.util.function.ToDoubleFunction;
* per thread, uid or call description.
*/
public class BinderCallsStats implements BinderInternal.Observer {
- public static final boolean ENABLED_DEFAULT = false;
+ public static final boolean ENABLED_DEFAULT = true;
public static final boolean DETAILED_TRACKING_DEFAULT = true;
public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 100;
public static final int MAX_BINDER_CALL_STATS_COUNT_DEFAULT = 5000;
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index 0b329d70f7af..c8d30b27d4dc 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -17,8 +17,9 @@
package com.android.internal.os;
import android.app.ApplicationLoaders;
+import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
-import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.WebViewFactory;
@@ -66,6 +67,34 @@ class WebViewZygoteInit {
}
@Override
+ protected boolean canPreloadApp() {
+ return true;
+ }
+
+ @Override
+ protected void handlePreloadApp(ApplicationInfo appInfo) {
+ Log.i(TAG, "Beginning application preload for " + appInfo.packageName);
+ LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false);
+ ClassLoader loader = loadedApk.getClassLoader();
+ doPreload(loader, WebViewFactory.getWebViewLibrary(appInfo));
+
+ // Add the APK to the Zygote's list of allowed files for children.
+ Zygote.nativeAllowFileAcrossFork(appInfo.sourceDir);
+ if (appInfo.splitSourceDirs != null) {
+ for (String path : appInfo.splitSourceDirs) {
+ Zygote.nativeAllowFileAcrossFork(path);
+ }
+ }
+ if (appInfo.sharedLibraryFiles != null) {
+ for (String path : appInfo.sharedLibraryFiles) {
+ Zygote.nativeAllowFileAcrossFork(path);
+ }
+ }
+
+ Log.i(TAG, "Application preload done");
+ }
+
+ @Override
protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName,
String cacheKey) {
Log.i(TAG, "Beginning package preload");
@@ -76,16 +105,22 @@ class WebViewZygoteInit {
ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
packagePath, libsPath, cacheKey);
- // Load the native library using WebViewLibraryLoader to share the RELRO data with other
- // processes.
- WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
-
// Add the APK to the Zygote's list of allowed files for children.
String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
for (String packageEntry : packageList) {
Zygote.nativeAllowFileAcrossFork(packageEntry);
}
+ doPreload(loader, libFileName);
+
+ Log.i(TAG, "Package preload done");
+ }
+
+ private void doPreload(ClassLoader loader, String libFileName) {
+ // Load the native library using WebViewLibraryLoader to share the RELRO data with other
+ // processes.
+ WebViewLibraryLoader.loadNativeLibrary(loader, libFileName);
+
// Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library
// and perform any other initialisation work that should be shared among the children.
@@ -114,8 +149,6 @@ class WebViewZygoteInit {
} catch (IOException ioe) {
throw new IllegalStateException("Error writing to command socket", ioe);
}
-
- Log.i(TAG, "Package preload done");
}
}
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index df89b265cd8e..24a08ca5b1e0 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -339,6 +339,8 @@ class ZygoteArguments {
mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
} else if (arg.equals("--mount-external-installer")) {
mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
+ } else if (arg.equals("--mount-external-legacy")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
} else if (arg.equals("--query-abi-list")) {
mAbiListQuery = true;
} else if (arg.equals("--get-pid")) {
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 97d5a657e5c4..2ee902ab6468 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -40,7 +40,7 @@ oneway interface IInputMethod {
void unbindInput();
void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods,
- in EditorInfo attribute, boolean restarting);
+ in EditorInfo attribute, boolean restarting, boolean preRenderImeViews);
void createSession(in InputChannel channel, IInputSessionCallback callback);
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index ad51c4701d84..5de088397690 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -19,6 +19,7 @@
#include <hwui/Paint.h>
#include <hwui/Bitmap.h>
#include <renderthread/RenderProxy.h>
+#include <utils/Color.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
@@ -602,6 +603,14 @@ static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) {
return static_cast<jint>(bitmap->getGenerationID());
}
+static jboolean Bitmap_isConfigF16(JNIEnv* env, jobject, jlong bitmapHandle) {
+ LocalScopedBitmap bitmap(bitmapHandle);
+ if (bitmap->info().colorType() == kRGBA_F16_SkColorType) {
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+}
+
static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
@@ -1120,7 +1129,8 @@ static jobject Bitmap_createHardwareBitmap(JNIEnv* env, jobject, jobject graphic
sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer));
// To support any color space, we need to pass an additional ColorSpace argument to
// java Bitmap.createHardwareBitmap.
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB());
+ SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, SkColorSpace::MakeSRGB());
if (!bitmap.get()) {
ALOGW("failed to create hardware bitmap from graphic buffer");
return NULL;
@@ -1133,7 +1143,8 @@ static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject har
AHardwareBuffer* hwBuf = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
hardwareBuffer);
sp<GraphicBuffer> buffer(AHardwareBuffer_to_GraphicBuffer(hwBuf));
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
+ SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct,
GraphicsJNI::getNativeColorSpace(colorSpacePtr));
if (!bitmap.get()) {
ALOGW("failed to create hardware bitmap from hardware buffer");
@@ -1193,6 +1204,7 @@ static const JNINativeMethod gBitmapMethods[] = {
{ "nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong },
{ "nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes },
{ "nativeConfig", "(J)I", (void*)Bitmap_config },
+ { "nativeIsConfigF16", "(J)Z", (void*)Bitmap_isConfigF16 },
{ "nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha },
{ "nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied},
{ "nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha},
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 7679c5b63274..cc22ff02e338 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -28,6 +28,8 @@
#include "SkBlurDrawLooper.h"
#include "SkColorFilter.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
#include "SkFontTypes.h"
#include "SkMaskFilter.h"
#include "SkPath.h"
@@ -69,9 +71,21 @@ static JMetricsID gFontMetrics_fieldID;
static jclass gFontMetricsInt_class;
static JMetricsID gFontMetricsInt_fieldID;
-static void defaultSettingsForAndroid(Paint* paint) {
- // GlyphID encoding is required because we are using Harfbuzz shaping
- paint->setTextEncoding(kGlyphID_SkTextEncoding);
+static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
+ const SkPoint pos[], SkPath* dst) {
+ struct Rec {
+ SkPath* fDst;
+ const SkPoint* fPos;
+ } rec = { dst, pos };
+ font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+ Rec* rec = (Rec*)ctx;
+ if (src) {
+ SkMatrix tmp(mx);
+ tmp.postTranslate(rec->fPos->fX, rec->fPos->fY);
+ rec->fDst->addPath(*src, tmp);
+ }
+ rec->fPos += 1;
+ }, &rec);
}
namespace PaintGlue {
@@ -88,18 +102,7 @@ namespace PaintGlue {
}
static jlong init(JNIEnv* env, jobject) {
- static_assert(1 << 0 == SkPaint::kAntiAlias_Flag, "paint_flags_mismatch");
- static_assert(1 << 2 == SkPaint::kDither_Flag, "paint_flags_mismatch");
- static_assert(1 << 3 == SkPaint::kUnderlineText_ReserveFlag, "paint_flags_mismatch");
- static_assert(1 << 4 == SkPaint::kStrikeThruText_ReserveFlag, "paint_flags_mismatch");
- static_assert(1 << 5 == SkPaint::kFakeBoldText_Flag, "paint_flags_mismatch");
- static_assert(1 << 6 == SkPaint::kLinearText_Flag, "paint_flags_mismatch");
- static_assert(1 << 7 == SkPaint::kSubpixelText_Flag, "paint_flags_mismatch");
- static_assert(1 << 10 == SkPaint::kEmbeddedBitmapText_Flag, "paint_flags_mismatch");
-
- Paint* obj = new Paint();
- defaultSettingsForAndroid(obj);
- return reinterpret_cast<jlong>(obj);
+ return reinterpret_cast<jlong>(new Paint);
}
static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) {
@@ -288,10 +291,11 @@ namespace PaintGlue {
pos[i].fX = x + layout.getX(i);
pos[i].fY = y + layout.getY(i);
}
+ const SkFont& font = paint->getSkFont();
if (start == 0) {
- paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path);
+ getPosTextPath(font, glyphs, end, pos, path);
} else {
- paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath);
+ getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath);
path->addPath(tmpPath);
}
}
@@ -321,7 +325,6 @@ namespace PaintGlue {
x += MinikinUtils::xOffsetForTextAlign(paint, layout);
Paint::Align align = paint->getTextAlign();
paint->setTextAlign(Paint::kLeft_Align);
- paint->setTextEncoding(kGlyphID_SkTextEncoding);
GetTextFunctor f(layout, path, x, y, paint, glyphs, pos);
MinikinUtils::forFontRun(layout, paint, f);
paint->setTextAlign(align);
@@ -584,20 +587,21 @@ namespace PaintGlue {
const int kElegantDescent = -500;
const int kElegantLeading = 0;
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ SkFont* font = &paint->getSkFont();
const Typeface* typeface = paint->getAndroidTypeface();
typeface = Typeface::resolveDefault(typeface);
minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
- float saveSkewX = paint->getTextSkewX();
- bool savefakeBold = paint->isFakeBoldText();
- MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery);
- SkScalar spacing = paint->getFontMetrics(metrics);
+ float saveSkewX = font->getSkewX();
+ bool savefakeBold = font->isEmbolden();
+ MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+ SkScalar spacing = font->getMetrics(metrics);
// The populateSkPaint call may have changed fake bold / text skew
// because we want to measure with those effects applied, so now
// restore the original settings.
- paint->setTextSkewX(saveSkewX);
- paint->setFakeBoldText(savefakeBold);
+ font->setSkewX(saveSkewX);
+ font->setEmbolden(savefakeBold);
if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) {
- SkScalar size = paint->getTextSize();
+ SkScalar size = font->getSize();
metrics->fTop = -size * kElegantTop / 2048;
metrics->fBottom = -size * kElegantBottom / 2048;
metrics->fAscent = -size * kElegantAscent / 2048;
@@ -646,9 +650,7 @@ namespace PaintGlue {
// ------------------ @CriticalNative ---------------------------
static void reset(jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- obj->reset();
- defaultSettingsForAndroid(obj);
+ reinterpret_cast<Paint*>(objHandle)->reset();
}
static void assign(jlong dstPaintHandle, jlong srcPaintHandle) {
@@ -657,40 +659,22 @@ namespace PaintGlue {
*dst = *src;
}
- // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
- static const uint32_t sFilterBitmapFlag = 0x02;
-
static jint getFlags(jlong paintHandle) {
- Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
- uint32_t result = nativePaint->getFlags();
- result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away.
- if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) {
- result |= sFilterBitmapFlag;
- }
- return static_cast<jint>(result);
+ uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags();
+ return static_cast<jint>(flags);
}
static void setFlags(jlong paintHandle, jint flags) {
- Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
- // Instead of modifying 0x02, change the filter level.
- nativePaint->setFilterQuality(flags & sFilterBitmapFlag
- ? kLow_SkFilterQuality
- : kNone_SkFilterQuality);
- // Don't pass through filter flag, which is no longer stored in paint's flags.
- flags &= ~sFilterBitmapFlag;
- // Use the existing value for 0x02.
- const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag;
- flags |= existing0x02Flag;
- nativePaint->setFlags(flags);
+ reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags);
}
static jint getHinting(jlong paintHandle) {
- return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getHinting()
+ return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getSkFont().getHinting()
== kNo_SkFontHinting ? 0 : 1;
}
static void setHinting(jlong paintHandle, jint mode) {
- reinterpret_cast<Paint*>(paintHandle)->setHinting(
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setHinting(
mode == 0 ? kNo_SkFontHinting : kNormal_SkFontHinting);
}
@@ -699,37 +683,23 @@ namespace PaintGlue {
}
static void setLinearText(jlong paintHandle, jboolean linearText) {
- reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText);
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText);
}
static void setSubpixelText(jlong paintHandle, jboolean subpixelText) {
- reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText);
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText);
}
static void setUnderlineText(jlong paintHandle, jboolean underlineText) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- uint32_t flags = paint->getFlags();
- if (underlineText) {
- flags |= Paint::kUnderlineText_ReserveFlag;
- } else {
- flags &= ~Paint::kUnderlineText_ReserveFlag;
- }
- paint->setFlags(flags);
+ reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText);
}
static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) {
- Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- uint32_t flags = paint->getFlags();
- if (strikeThruText) {
- flags |= Paint::kStrikeThruText_ReserveFlag;
- } else {
- flags &= ~Paint::kStrikeThruText_ReserveFlag;
- }
- paint->setFlags(flags);
+ reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText);
}
static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) {
- reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText);
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText);
}
static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) {
@@ -907,27 +877,29 @@ namespace PaintGlue {
}
static jfloat getTextSize(jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize());
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize());
}
static void setTextSize(jlong paintHandle, jfloat textSize) {
- reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize);
+ if (textSize >= 0) {
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize);
+ }
}
static jfloat getTextScaleX(jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX());
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX());
}
static void setTextScaleX(jlong paintHandle, jfloat scaleX) {
- reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX);
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX);
}
static jfloat getTextSkewX(jlong paintHandle) {
- return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX());
+ return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX());
}
static void setTextSkewX(jlong paintHandle, jfloat skewX) {
- reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX);
+ reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX);
}
static jfloat getLetterSpacing(jlong paintHandle) {
@@ -979,7 +951,7 @@ namespace PaintGlue {
if (metrics.hasUnderlinePosition(&position)) {
return SkScalarToFloat(position);
} else {
- const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+ const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
return SkScalarToFloat(Paint::kStdUnderline_Top * textSize);
}
}
@@ -991,18 +963,18 @@ namespace PaintGlue {
if (metrics.hasUnderlineThickness(&thickness)) {
return SkScalarToFloat(thickness);
} else {
- const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+ const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize);
}
}
static jfloat getStrikeThruPosition(jlong paintHandle) {
- const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+ const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize);
}
static jfloat getStrikeThruThickness(jlong paintHandle) {
- const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+ const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize);
}
diff --git a/core/jni/android/graphics/PaintFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp
index 182b22b3c917..4fe9140572d3 100644
--- a/core/jni/android/graphics/PaintFilter.cpp
+++ b/core/jni/android/graphics/PaintFilter.cpp
@@ -21,6 +21,7 @@
#include "core_jni_helpers.h"
+#include "hwui/Paint.h"
#include "hwui/PaintFilter.h"
#include "SkPaint.h"
@@ -29,11 +30,15 @@ namespace android {
class PaintFlagsFilter : public PaintFilter {
public:
PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) {
- fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags);
- fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags);
+ fClearFlags = static_cast<uint16_t>(clearFlags);
+ fSetFlags = static_cast<uint16_t>(setFlags);
}
void filter(SkPaint* paint) override {
- paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+ uint32_t flags = Paint::GetSkPaintJavaFlags(*paint);
+ Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags);
+ }
+ void filterFullPaint(Paint* paint) override {
+ paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags);
}
private:
@@ -41,33 +46,6 @@ private:
uint16_t fSetFlags;
};
-// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality.
-class CompatPaintFlagsFilter : public PaintFlagsFilter {
-public:
- CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality)
- : PaintFlagsFilter(clearFlags, setFlags)
- , fDesiredQuality(desiredQuality) {
- }
-
- virtual void filter(SkPaint* paint) {
- PaintFlagsFilter::filter(paint);
- paint->setFilterQuality(fDesiredQuality);
- }
-
-private:
- const SkFilterQuality fDesiredQuality;
-};
-
-// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it.
-static inline bool hadFiltering(jint& flags) {
- // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
- static const uint32_t sFilterBitmapFlag = 0x02;
-
- const bool result = (flags & sFilterBitmapFlag) != 0;
- flags &= ~sFilterBitmapFlag;
- return result;
-}
-
class PaintFilterGlue {
public:
@@ -78,29 +56,11 @@ public:
static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz,
jint clearFlags, jint setFlags) {
+ PaintFilter* filter = nullptr;
if (clearFlags | setFlags) {
- // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
- // longer has a Skia equivalent flag (instead it corresponds to
- // calling setFilterQuality), and keep track of which group(s), if
- // any, had the flag set.
- const bool turnFilteringOn = hadFiltering(setFlags);
- const bool turnFilteringOff = hadFiltering(clearFlags);
-
- PaintFilter* filter;
- if (turnFilteringOn) {
- // Turning filtering on overrides turning it off.
- filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
- kLow_SkFilterQuality);
- } else if (turnFilteringOff) {
- filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
- kNone_SkFilterQuality);
- } else {
- filter = new PaintFlagsFilter(clearFlags, setFlags);
- }
- return reinterpret_cast<jlong>(filter);
- } else {
- return NULL;
+ filter = new PaintFlagsFilter(clearFlags, setFlags);
}
+ return reinterpret_cast<jlong>(filter);
}
};
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
index 38f7a7e25389..3204317cab68 100644..120000
--- a/core/jni/android_media_MediaMetricsJNI.cpp
+++ b/core/jni/android_media_MediaMetricsJNI.cpp
@@ -1,90 +1 @@
-/*
- * Copyright 2017, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <android_runtime/AndroidRuntime.h>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "android_media_MediaMetricsJNI.h"
-#include <media/MediaAnalyticsItem.h>
-
-
-namespace android {
-
-// place the attributes into a java PersistableBundle object
-jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) {
-
- jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
- if (clazzBundle==NULL) {
- ALOGD("can't find android/os/PersistableBundle");
- return NULL;
- }
- // sometimes the caller provides one for us to fill
- if (mybundle == NULL) {
- // create the bundle
- jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
- mybundle = env->NewObject(clazzBundle, constructID);
- if (mybundle == NULL) {
- return NULL;
- }
- }
-
- // grab methods that we can invoke
- jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
- jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
- jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
- jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
-
- // env, class, method, {parms}
- //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint);
-
- // iterate through my attributes
- // -- get name, get type, get value
- // -- insert appropriately into the bundle
- for (size_t i = 0 ; i < item->mPropCount; i++ ) {
- MediaAnalyticsItem::Prop *prop = &item->mProps[i];
- // build the key parameter from prop->mName
- jstring keyName = env->NewStringUTF(prop->mName);
- // invoke the appropriate method to insert
- switch (prop->mType) {
- case MediaAnalyticsItem::kTypeInt32:
- env->CallVoidMethod(mybundle, setIntID,
- keyName, (jint) prop->u.int32Value);
- break;
- case MediaAnalyticsItem::kTypeInt64:
- env->CallVoidMethod(mybundle, setLongID,
- keyName, (jlong) prop->u.int64Value);
- break;
- case MediaAnalyticsItem::kTypeDouble:
- env->CallVoidMethod(mybundle, setDoubleID,
- keyName, (jdouble) prop->u.doubleValue);
- break;
- case MediaAnalyticsItem::kTypeCString:
- env->CallVoidMethod(mybundle, setStringID, keyName,
- env->NewStringUTF(prop->u.CStringValue));
- break;
- default:
- ALOGE("to_String bad item type: %d for %s",
- prop->mType, prop->mName);
- break;
- }
- }
-
- return mybundle;
-}
-
-}; // namespace android
-
+../../media/jni/android_media_MediaMetricsJNI.cpp \ No newline at end of file
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
index b3cb4d293399..c7a685beb7e5 100644..120000
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -1,33 +1 @@
-/*
- * Copyright 2017, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
-
-namespace android {
-
-class MediaMetricsJNI {
-public:
- static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
-};
-
-}; // namespace android
-
-#endif // _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
+../../media/jni/android_media_MediaMetricsJNI.h \ No newline at end of file
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 69877c7d3930..f1b259e10cf5 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -124,7 +124,7 @@ static jlong nativeGetNativeTransactionFinalizer(JNIEnv* env, jclass clazz) {
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
- jint windowType, jint ownerUid) {
+ jobject metadataParcel) {
ScopedUtfChars name(env, nameStr);
sp<SurfaceComposerClient> client;
if (sessionObj != NULL) {
@@ -134,8 +134,18 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
}
SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
sp<SurfaceControl> surface;
+ LayerMetadata metadata;
+ Parcel* parcel = parcelForJavaObject(env, metadataParcel);
+ if (parcel && !parcel->objectsCount()) {
+ status_t err = metadata.readFromParcel(parcel);
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Metadata parcel has wrong format");
+ }
+ }
+
status_t err = client->createSurfaceChecked(
- String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid);
+ String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata));
if (err == NAME_NOT_FOUND) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return 0;
@@ -377,6 +387,28 @@ static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactio
transaction->transferTouchFocus(fromToken, toToken);
}
+static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint id, jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ if (!parcel) {
+ jniThrowNullPointerException(env, "attribute data");
+ return;
+ }
+ if (parcel->objectsCount()) {
+ jniThrowException(env, "java/lang/RuntimeException",
+ "Tried to marshall a Parcel that contained Binder objects.");
+ return;
+ }
+
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ std::vector<uint8_t> byteData(parcel->dataSize());
+ memcpy(byteData.data(), parcel->data(), parcel->dataSize());
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setMetadata(ctrl, id, std::move(byteData));
+}
+
static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jfloatArray fColor) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -981,7 +1013,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz,
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
- {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJII)J",
+ {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
(void*)nativeCreate },
{"nativeReadFromParcel", "(Landroid/os/Parcel;)J",
(void*)nativeReadFromParcel },
@@ -1099,6 +1131,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetInputWindowInfo },
{"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V",
(void*)nativeTransferTouchFocus },
+ {"nativeSetMetadata", "(JILandroid/os/Parcel;)V",
+ (void*)nativeSetMetadata },
{"nativeGetDisplayedContentSamplingAttributes",
"(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;",
(void*)nativeGetDisplayedContentSamplingAttributes },
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 318ec9b2ff0d..40529191a42c 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -1038,8 +1038,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(
// Continue I guess?
}
+ SkColorType ct = uirenderer::PixelFormatToColorType(buffer->getPixelFormat());
sk_sp<SkColorSpace> cs = uirenderer::DataSpaceToColorSpace(bufferItem.mDataSpace);
- sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, ct, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8681d4b3f42e..6ee960668a3e 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -821,7 +821,7 @@ static bool NeedsNoRandomizeWorkaround() {
// Utility to close down the Zygote socket file descriptors while
// the child is still running as root with Zygote's privileges. Each
-// descriptor (if any) is closed via dup2(), replacing it with a valid
+// descriptor (if any) is closed via dup3(), replacing it with a valid
// (open) descriptor to /dev/null.
static void DetachDescriptors(JNIEnv* env,
@@ -829,15 +829,15 @@ static void DetachDescriptors(JNIEnv* env,
fail_fn_t fail_fn) {
if (fds_to_close.size() > 0) {
- android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+ android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC));
if (devnull_fd == -1) {
fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
for (int fd : fds_to_close) {
ALOGV("Switching descriptor %d to /dev/null", fd);
- if (dup2(devnull_fd, fd) == -1) {
- fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+ if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) {
+ fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno)));
}
}
}
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 53dde80edd89..4b37f13cbb33 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -72,6 +72,7 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const {
return true;
}
+ // Framework jars are allowed.
static const char* kFrameworksPrefix = "/system/framework/";
static const char* kJarSuffix = ".jar";
if (android::base::StartsWith(path, kFrameworksPrefix)
@@ -79,6 +80,13 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const {
return true;
}
+ // Jars from the runtime apex are allowed.
+ static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/";
+ if (android::base::StartsWith(path, kRuntimeApexPrefix)
+ && android::base::EndsWith(path, kJarSuffix)) {
+ return true;
+ }
+
// Whitelist files needed for Runtime Resource Overlay, like these:
// /system/vendor/overlay/framework-res.apk
// /system/vendor/overlay-subdir/pg/framework-res.apk
@@ -415,13 +423,13 @@ bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
}
void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
- const int dev_null_fd = open("/dev/null", O_RDWR);
+ const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
if (dev_null_fd < 0) {
fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
}
- if (dup2(dev_null_fd, fd) == -1) {
- fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+ if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
+ fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
fd,
strerror(errno)));
}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 6cdba33a9ada..eb716ac280e2 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2183,4 +2183,10 @@ enum PageId {
// OPEN: Settings > Display > Adaptive sleep
// OS: Q
SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+ // OPEN: Settings > System > Aware
+ SETTINGS_AWARE = 1632;
+
+ // OPEN: Settings > System > Aware > Disable > Dialog
+ DIALOG_AWARE_DISABLE = 1633;
}
diff --git a/core/proto/android/hardware/biometrics/enums.proto b/core/proto/android/hardware/biometrics/enums.proto
new file mode 100644
index 000000000000..91f2acbbaf03
--- /dev/null
+++ b/core/proto/android/hardware/biometrics/enums.proto
@@ -0,0 +1,46 @@
+/*
+ * 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.hardware.biometrics;
+
+option java_outer_classname = "BiometricsProtoEnums";
+option java_multiple_files = true;
+
+// Logging constants for <Biometric>Service and BiometricService
+
+enum ModalityEnum {
+ MODALITY_UNKNOWN = 0;
+ MODALITY_FINGERPRINT = 1; // 1 << 0
+ MODALITY_IRIS = 2; // 1 << 1
+ MODALITY_FACE = 4; // 1 << 2
+}
+
+enum ClientEnum {
+ CLIENT_UNKNOWN = 0;
+ CLIENT_KEYGUARD = 1;
+ CLIENT_BIOMETRIC_PROMPT = 2;
+ CLIENT_FINGERPRINT_MANAGER = 3; // Deprecated API before BiometricPrompt was introduced
+}
+
+enum ActionEnum {
+ ACTION_UNKNOWN = 0;
+ ACTION_ENROLL = 1;
+ ACTION_AUTHENTICATE = 2;
+ ACTION_ENUMERATE = 3;
+ ACTION_REMOVE = 4;
+} \ No newline at end of file
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index d79eb9402132..7e7942e6ddf1 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -451,6 +451,8 @@ message GlobalSettingsProto {
optional SettingProto gup_blacklist = 11;
// List of Apps that are allowed to use Game Driver package.
optional SettingProto game_driver_whitelist = 12;
+ // ANGLE - List of Apps that can check ANGLE rules
+ optional SettingProto angle_whitelist = 13;
}
optional Gpu gpu = 59;
@@ -520,6 +522,8 @@ message GlobalSettingsProto {
optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto gnss_hal_location_request_duration_millis = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Packages that are whitelisted for ignoring location settings (during emergencies)
+ optional SettingProto ignore_settings_package_whitelist = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Location location = 69;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index c0d6139b117a..aaf6c63b2978 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -530,7 +530,9 @@ message SecureSettingsProto {
optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto aware_enabled = 77 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
// 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/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index e68f9dbbc9b7..188769d930d1 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -31,6 +31,7 @@ import "frameworks/base/core/proto/android/os/persistablebundle.proto";
import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
import "frameworks/base/libs/incident/proto/android/privacy.proto";
+// Next tag: 21
message JobSchedulerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -139,9 +140,13 @@ message JobSchedulerServiceDumpProto {
// The current limit on the number of concurrent JobServiceContext entries
// we want to keep actively running a job.
optional int32 max_active_jobs = 13;
+
+ // Dump from JobConcurrencyManager.
+ optional JobConcurrencyManagerProto concurrency_manager = 20;
}
// A com.android.server.job.JobSchedulerService.Constants object.
+// Next tag: 29
message ConstantsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -273,7 +278,39 @@ message ConstantsProto {
}
optional QuotaController quota_controller = 24;
- // Next tag: 26
+ // Max number of jobs, when screen is ON.
+ optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_on = 26;
+
+ // Max number of jobs, when screen is OFF.
+ optional MaxJobCountsPerMemoryTrimLevelProto max_job_counts_screen_off = 27;
+
+ // In this time after screen turns on, we increase job concurrency.
+ optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
+}
+
+// Next tag: 4
+message MaxJobCountsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Total number of jobs to run simultaneously.
+ optional int32 total_jobs = 1;
+
+ // Max number of BG (== owned by non-TOP apps) jobs to run simultaneously.
+ optional int32 max_bg = 2;
+
+ // We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
+ // pending, rather than always running the TOTAL number of FG jobs.
+ optional int32 min_bg = 3;
+}
+
+// Next tag: 5
+message MaxJobCountsPerMemoryTrimLevelProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional MaxJobCountsProto normal = 1;
+ optional MaxJobCountsProto moderate = 2;
+ optional MaxJobCountsProto low = 3;
+ optional MaxJobCountsProto critical = 4;
}
message StateControllerProto {
@@ -807,3 +844,46 @@ message JobStatusDumpProto {
// Next tag: 28
}
+
+// Dump from com.android.server.job.JobConcurrencyManager.
+// Next tag: 7
+message JobConcurrencyManagerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Whether the device is interactive (== screen on) now or not.
+ optional bool current_interactive = 1;
+ // Similar to current_interactive, screen on or not, but it takes into account the off timeout.
+ optional bool effective_interactive = 2;
+ // How many milliseconds have passed since the last screen on. (i.e. 1000 == 1 sec ago)
+ optional int64 time_since_last_screen_on_ms = 3;
+ // How many milliseconds have passed since the last screen off. (i.e. 1000 == 1 sec ago)
+ optional int64 time_since_last_screen_off_ms = 4;
+ // Current max number of jobs.
+ optional JobCountTrackerProto job_count_tracker = 5;
+ // Current memory trim level.
+ optional int32 memory_trim_level = 6;
+}
+
+// Dump from com.android.server.job.JobConcurrencyManager.JobCountTracker.
+// Next tag: 8
+message JobCountTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Number of total jos that can run simultaneously.
+ optional int32 config_num_max_total_jobs = 1;
+ // Number of background jos that can run simultaneously.
+ optional int32 config_num_max_bg_jobs = 2;
+ // Out of total jobs, this many background jobs should be guaranteed to be executed, even if
+ // there are the config_num_max_total_jobs count of foreground jobs pending.
+ optional int32 config_num_min_bg_jobs = 3;
+
+ // Number of running foreground jobs.
+ optional int32 num_running_fg_jobs = 4;
+ // Number of running background jobs.
+ optional int32 num_running_bg_jobs = 5;
+
+ // Number of pending foreground jobs.
+ optional int32 num_pending_fg_jobs = 6;
+ // Number of pending background jobs.
+ optional int32 num_pending_bg_jobs = 7;
+}
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index 00fae3d83ebc..367c54086ade 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -228,6 +228,15 @@ message UsbPortProto {
repeated Mode supported_modes = 2;
}
+/* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */
+enum ContaminantPresenceStatus {
+ CONTAMINANT_STATUS_UNKNOWN = 0;
+ CONTAMINANT_STATUS_NOT_SUPPORTED = 1;
+ CONTAMINANT_STATUS_DISABLED = 2;
+ CONTAMINANT_STATUS_NOT_DETECTED = 3;
+ CONTAMINANT_STATUS_DETECTED = 4;
+}
+
message UsbPortStatusProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -245,14 +254,6 @@ message UsbPortStatusProto {
DATA_ROLE_DEVICE = 2;
}
- /* Same as android.hardware.usb.V1_2.Constants.ContaminantPresenceStatus */
- enum ContaminantPresenceStatus {
- CONTAMINANT_STATUS_NOT_SUPPORTED = 0;
- CONTAMINANT_STATUS_DISABLED = 1;
- CONTAMINANT_STATUS_NOT_DETECTED = 2;
- CONTAMINANT_STATUS_DETECTED = 3;
- }
-
optional bool connected = 1;
optional UsbPortProto.Mode current_mode = 2;
optional PowerRole power_role = 3;
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 82460ec4ed8b..a8e64c6d8324 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -92,8 +92,7 @@ enum EventId {
SET_UNINSTALL_BLOCKED = 67;
SET_PACKAGES_SUSPENDED = 68;
ON_LOCK_TASK_MODE_ENTERING = 69;
- ADD_CROSS_PROFILE_CALENDAR_PACKAGE = 70;
- REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE = 71;
+ SET_CROSS_PROFILE_CALENDAR_PACKAGES = 70;
GET_USER_PASSWORD_COMPLEXITY_LEVEL = 72;
INSTALL_SYSTEM_UPDATE = 73;
INSTALL_SYSTEM_UPDATE_ERROR = 74;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f92df6a070c6..25baa921e8c9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -43,7 +43,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
- <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" />
+ <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
@@ -2119,7 +2119,7 @@
<!-- ================================== -->
<eat-comment />
- <!-- @SystemApi Allows an application to write to internal media storage
+ <!-- @SystemApi @TestApi Allows an application to write to internal media storage
@hide -->
<permission android:name="android.permission.WRITE_MEDIA_STORAGE"
android:protectionLevel="signature|privileged" />
@@ -3329,7 +3329,7 @@
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows an application to clear user data.
+ <!-- @SystemApi @TestApi Allows an application to clear user data.
<p>Not for use by third-party applications
@hide
-->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dd8ecdbc93b5..c05795de4751 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -957,7 +957,7 @@
<!-- Default mode to control how Night display is automatically activated.
One of the following values (see ColorDisplayController.java):
0 - AUTO_MODE_DISABLED
- 1 - AUTO_MODE_CUSTOM
+ 1 - AUTO_MODE_CUSTOM_TIME
2 - AUTO_MODE_TWILIGHT
-->
<integer name="config_defaultNightDisplayAutoMode">0</integer>
@@ -2211,6 +2211,10 @@
has expired, then assume the device is receiving insufficient current to charge
effectively and terminate the dream. Use -1 to disable this safety feature. -->
<integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
+ <!-- Limit of how long the device can remain unlocked due to attention checking. -->
+ <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes -->
+ <!-- How long we should wait until we give up on receiving an attention API callback. -->
+ <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds -->
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
@@ -3729,9 +3733,6 @@
<!-- Whether or not the "SMS app service" feature is enabled -->
<bool name="config_useSmsAppService">true</bool>
- <!-- Component name for default assistant on this device -->
- <string name="config_defaultAssistantComponentName">#+UNSET</string>
-
<!-- Class name for the InputEvent compatibility processor override.
Empty string means use the default compatibility processor
(android.view.InputEventCompatProcessor). -->
@@ -3762,4 +3763,7 @@
<!-- Whether cbrs is supported on the device or not -->
<bool translatable="false" name="config_cbrs_supported">false</bool>
+
+ <!-- Whether or not aware is enabled by default -->
+ <bool name="config_awareSettingAvailable">false</bool>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4235341b626b..ec1bac1a41d6 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2988,7 +2988,7 @@
</public-group>
<public-group type="array" first-id="0x01070006">
- <!-- @hide @SystemApi -->
+ <!-- @hide @TestApi @SystemApi -->
<public name="config_defaultRoleHolders" />
</public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 65a895938774..a761baf95b0d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3387,6 +3387,9 @@
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
<string name="wifi_no_internet_detailed">Tap for options</string>
+ <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
+ <string name="captive_portal_logged_in_detailed">Connected</string>
+
<!-- A notification is shown when the user's softap config has been changed due to underlying
hardware restrictions. This is the notifications's title.
[CHAR_LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5991d88349d5..f79e22d1f94e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -695,6 +695,7 @@
<java-symbol type="string" name="capability_title_canControlMagnification" />
<java-symbol type="string" name="capability_desc_canPerformGestures" />
<java-symbol type="string" name="capability_title_canPerformGestures" />
+ <java-symbol type="string" name="captive_portal_logged_in_detailed" />
<java-symbol type="string" name="cfTemplateForwarded" />
<java-symbol type="string" name="cfTemplateForwardedTime" />
<java-symbol type="string" name="cfTemplateNotForwarded" />
@@ -3527,8 +3528,6 @@
<java-symbol type="bool" name="config_useSmsAppService" />
- <java-symbol type="string" name="config_defaultAssistantComponentName" />
-
<java-symbol type="id" name="transition_overlay_view_tag" />
<java-symbol type="dimen" name="rounded_corner_radius" />
@@ -3553,4 +3552,10 @@
<!-- For CBRS -->
<java-symbol type="bool" name="config_cbrs_supported" />
+
+ <java-symbol type="bool" name="config_awareSettingAvailable" />
+
+ <!-- For Attention Service -->
+ <java-symbol type="integer" name="config_attentionMaximumExtension" />
+ <java-symbol type="integer" name="config_attentionApiTimeout" />
</resources>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 0fc3bd224fbf..8e8b07a9074b 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -49,6 +49,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
LOCAL_JAVA_LIBRARIES := \
android.test.runner \
telephony-common \
+ testables \
org.apache.http.legacy \
android.test.base \
android.test.mock \
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 8604b0c48476..bdf3aa2563db 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -38,7 +38,6 @@ import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -501,7 +500,7 @@ public class TransactionParcelTests {
}
@Override
- public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException {
+ public void updateHttpProxy() throws RemoteException {
}
@Override
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 3d7aab001227..ad9814bd01b1 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -48,21 +48,11 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate
}
/**
- * Detect when the org.apache.http.legacy is not on the bootclasspath.
- *
- * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
- * succeed otherwise. This allows a developer to ensure that the tests are being
- */
- @Test
- public void detectWhenOAHLisOnBCP() {
- Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL());
- }
-
- /**
* Detect when the android.test.base is not on the bootclasspath.
*
* <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
- * succeed otherwise. This allows a developer to ensure that the tests are being
+ * succeed otherwise. This allows a developer to ensure that the tests are being run in the
+ * correct environment.
*/
@Test
public void detectWhenATBisOnBCP() {
@@ -85,9 +75,7 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate
if (!PackageBackwardCompatibility.bootClassPathContainsATB()) {
expected.add(ANDROID_TEST_BASE);
}
- if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) {
- expected.add(ORG_APACHE_HTTP_LEGACY);
- }
+ expected.add(ORG_APACHE_HTTP_LEGACY);
PackageBuilder after = builder()
.targetSdkVersion(Build.VERSION_CODES.O)
@@ -98,30 +86,6 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate
/**
* Ensures that the {@link PackageBackwardCompatibility} uses
- * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}
- * when necessary.
- *
- * <p>More comprehensive tests for that class can be found in
- * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}.
- */
- @Test
- public void org_apache_http_legacy_in_usesLibraries() {
- Assume.assumeTrue("Test requires that "
- + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath",
- PackageBackwardCompatibility.bootClassPathContainsOAHL());
-
- PackageBuilder before = builder()
- .requiredLibraries(ORG_APACHE_HTTP_LEGACY);
-
- // org.apache.http.legacy should be removed from the libraries because it is provided
- // on the bootclasspath and providing both increases start up cost unnecessarily.
- PackageBuilder after = builder();
-
- checkBackwardsCompatibility(before, after);
- }
-
- /**
- * Ensures that the {@link PackageBackwardCompatibility} uses
* {@link RemoveUnnecessaryAndroidTestBaseLibrary}
* when necessary.
*
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 7b92cf50ee21..300394d426e4 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -527,12 +527,14 @@ public class PackageParserTest {
R.raw.com_android_tzdata);
PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false);
assertEquals("com.google.android.tzdata", pi.packageName);
+ assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertEquals(1, pi.getLongVersionCode());
assertEquals(1, pi.applicationInfo.longVersionCode);
assertNull(pi.signingInfo);
pi = PackageParser.generatePackageInfoFromApex(apexFile, true);
assertEquals("com.google.android.tzdata", pi.packageName);
+ assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertEquals(1, pi.getLongVersionCode());
assertEquals(1, pi.applicationInfo.longVersionCode);
assertNotNull(pi.signingInfo);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index a010cb6576f2..bd7f8527fc6f 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,7 @@ public class SettingsBackupTest {
Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS,
Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
@@ -390,9 +391,8 @@ public class SettingsBackupTest {
Settings.Global.POWER_MANAGER_CONSTANTS,
Settings.Global.PREFERRED_NETWORK_MODE,
Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED,
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED,
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED,
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED,
Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
Settings.Global.RADIO_BLUETOOTH,
@@ -482,6 +482,7 @@ public class SettingsBackupTest {
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
Settings.Global.GUP_DEV_ALL_APPS,
Settings.Global.GUP_DEV_OPT_IN_APPS,
Settings.Global.GUP_DEV_OPT_OUT_APPS,
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index d44745121a22..8f2109676dfb 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,15 +16,25 @@
package android.view;
+import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets.Type;
+import android.view.WindowManager.BadTokenException;
+import android.view.WindowManager.LayoutParams;
+import android.widget.TextView;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,8 +47,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class InsetsControllerTest {
- private InsetsController mController = new InsetsController(mock(ViewRootImpl.class));
-
+ private InsetsController mController;
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mLeash;
@@ -47,6 +56,24 @@ public class InsetsControllerTest {
mLeash = new SurfaceControl.Builder(mSession)
.setName("testSurface")
.build();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ Context context = InstrumentationRegistry.getTargetContext();
+ // cannot mock ViewRootImpl since it's final.
+ ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+ try {
+ viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+ } catch (BadTokenException e) {
+ // activity isn't running, we will ignore BadTokenException.
+ }
+ mController = new InsetsController(viewRootImpl);
+ final Rect rect = new Rect(5, 5, 5, 5);
+ mController.calculateInsets(
+ false,
+ false,
+ new DisplayCutout(
+ Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
+ rect, rect);
+ });
}
@Test
@@ -64,4 +91,39 @@ public class InsetsControllerTest {
mController.onControlsChanged(new InsetsSourceControl[0]);
assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl());
}
+
+ @Test
+ public void testAnimationEndState() {
+ final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash);
+ final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash);
+ final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash);
+
+ InsetsSourceControl[] controls = new InsetsSourceControl[3];
+ controls[0] = navBar;
+ controls[1] = topBar;
+ controls[2] = ime;
+ mController.onControlsChanged(controls);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mController.show(Type.all());
+ // quickly jump to final state by cancelling it.
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ mController.hide(Type.all());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ mController.show(Type.ime());
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ mController.hide(Type.ime());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ });
+ }
}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index d57fa8f9f612..6a83c29b0943 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -20,6 +20,7 @@ import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.sideBars;
import static android.view.WindowInsets.Type.topBar;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.graphics.Insets;
@@ -73,4 +74,29 @@ public class WindowInsetsTest {
assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar()));
assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars()));
}
+
+ // TODO: Move this to CTS once API made public
+ @Test
+ public void visibility() {
+ Builder b = new WindowInsets.Builder();
+ b.setInsets(sideBars(), Insets.of(0, 0, 0, 100));
+ b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+ b.setVisible(sideBars(), true);
+ b.setVisible(ime(), true);
+ WindowInsets insets = b.build();
+ assertTrue(insets.isVisible(sideBars()));
+ assertTrue(insets.isVisible(sideBars() | ime()));
+ assertFalse(insets.isVisible(sideBars() | topBar()));
+ }
+
+ // TODO: Move this to CTS once API made public
+ @Test
+ public void consume_doesntChangeVisibility() {
+ Builder b = new WindowInsets.Builder();
+ b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+ b.setVisible(ime(), true);
+ WindowInsets insets = b.build();
+ insets = insets.consumeSystemWindowInsets();
+ assertTrue(insets.isVisible(ime()));
+ }
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index 780e15ab885e..5022e305ecc2 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
@@ -16,8 +16,8 @@
package android.view.textclassifier;
-import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL;
-import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF;
import static com.google.common.truth.Truth.assertThat;
@@ -58,7 +58,7 @@ public class ActionsSuggestionsHelperTest {
@Test
public void testToNativeMessages_noTextMessages() {
ConversationActions.Message messageWithoutText =
- new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build();
+ new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build();
ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
ActionsSuggestionsHelper.toNativeMessages(
@@ -81,7 +81,7 @@ public class ActionsSuggestionsHelperTest {
.setText("second")
.build();
ConversationActions.Message thirdMessage =
- new ConversationActions.Message.Builder(PERSON_USER_LOCAL)
+ new ConversationActions.Message.Builder(PERSON_USER_SELF)
.setText("third")
.build();
ConversationActions.Message fourthMessage =
@@ -104,16 +104,16 @@ public class ActionsSuggestionsHelperTest {
@Test
public void testToNativeMessages_referenceTime() {
ConversationActions.Message firstMessage =
- new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+ new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
.setText("first")
.setReferenceTime(createZonedDateTimeFromMsUtc(1000))
.build();
ConversationActions.Message secondMessage =
- new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+ new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
.setText("second")
.build();
ConversationActions.Message thirdMessage =
- new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+ new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
.setText("third")
.setReferenceTime(createZonedDateTimeFromMsUtc(2000))
.build();
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 7009fb2ea758..5e58f82038f1 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -262,6 +262,9 @@ public class TextClassifierTest {
assertEquals(
context.getString(com.android.internal.R.string.translate),
classification.getActions().get(0).getTitle());
+ Intent intent = (Intent) classification.getExtras()
+ .getParcelableArrayList(TextClassifierImpl.ACTIONS_INTENTS).get(0);
+ assertEquals(Intent.ACTION_TRANSLATE, intent.getAction());
LocaleList.setDefault(originalLocales);
}
@@ -375,7 +378,7 @@ public class TextClassifierTest {
if (isTextClassifierDisabled()) return;
ConversationActions.Message message =
new ConversationActions.Message.Builder(
- ConversationActions.Message.PERSON_USER_REMOTE)
+ ConversationActions.Message.PERSON_USER_OTHERS)
.setText("Where are you?")
.build();
TextClassifier.EntityConfig typeConfig =
@@ -404,7 +407,7 @@ public class TextClassifierTest {
if (isTextClassifierDisabled()) return;
ConversationActions.Message message =
new ConversationActions.Message.Builder(
- ConversationActions.Message.PERSON_USER_REMOTE)
+ ConversationActions.Message.PERSON_USER_OTHERS)
.setText("Where are you?")
.build();
TextClassifier.EntityConfig typeConfig =
diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
index b1b74160ecd5..73af56743b5f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/logging/TextClassifierEventTronLoggerTest.java
@@ -18,9 +18,10 @@ package android.view.textclassifier.logging;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.CONVERSATION_ACTIONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_EVENT_TIME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_SCORE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_TEXT_CLASSIFIER_WIDGET_TYPE;
import static com.google.common.truth.Truth.assertThat;
@@ -71,7 +72,8 @@ public class TextClassifierEventTronLoggerTest {
new TextClassifierEvent.Builder(
TextClassifierEvent.CATEGORY_CONVERSATION_ACTIONS,
TextClassifierEvent.TYPE_SMART_ACTION)
- .setEntityType(ConversationAction.TYPE_CALL_PHONE)
+ .setEntityTypes(ConversationAction.TYPE_CALL_PHONE)
+ .setScore(0.5f)
.setEventTime(EVENT_TIME)
.setEventContext(textClassificationContext)
.build();
@@ -83,15 +85,18 @@ public class TextClassifierEventTronLoggerTest {
LogMaker logMaker = captor.getValue();
assertThat(logMaker.getCategory()).isEqualTo(
CONVERSATION_ACTIONS);
- assertThat(logMaker.getType()).isEqualTo(
+ assertThat(logMaker.getSubtype()).isEqualTo(
ACTION_TEXT_SELECTION_SMART_SHARE);
- assertThat(logMaker.getTaggedData(FIELD_SELECTION_ENTITY_TYPE))
+ assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE))
.isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+ assertThat((float) logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_SCORE))
+ .isWithin(0.00001f).of(0.5f);
assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_EVENT_TIME))
.isEqualTo(EVENT_TIME);
assertThat(logMaker.getPackageName()).isEqualTo(PACKAGE_NAME);
- assertThat(logMaker.getTaggedData(FIELD_SELECTION_WIDGET_TYPE))
+ assertThat(logMaker.getTaggedData(FIELD_TEXT_CLASSIFIER_WIDGET_TYPE))
.isEqualTo(WIDGET_TYPE);
+
}
@Test
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index bfbdbc585e08..8636949943b7 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1711,20 +1711,22 @@ public final class Bitmap implements Parcelable {
*/
@Nullable
public final ColorSpace getColorSpace() {
- // A reconfigure can change the configuration and rgba16f is
- // always linear scRGB at this time
- if (getConfig() == Config.RGBA_F16) {
- // Reset the color space for potential future reconfigurations
- mColorSpace = null;
- return ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
- }
-
+ checkRecycled("getColorSpace called on a recycled bitmap");
// Cache the color space retrieval since it can be fairly expensive
if (mColorSpace == null) {
- if (nativeIsSRGB(mNativePtr)) {
+ if (nativeIsConfigF16(mNativePtr)) {
+ // an F16 bitmaps is intended to always be linear extended, but due to
+ // inconsistencies in Bitmap.create() functions it is possible to have
+ // rendered into a bitmap in non-linear sRGB.
+ if (nativeIsSRGB(mNativePtr)) {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+ } else {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ }
+ } else if (nativeIsSRGB(mNativePtr)) {
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
- } else if (getConfig() == Config.HARDWARE && nativeIsSRGBLinear(mNativePtr)) {
- mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+ } else if (nativeIsSRGBLinear(mNativePtr)) {
+ mColorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB);
} else {
float[] xyz = new float[9];
float[] params = new float[7];
@@ -2127,6 +2129,7 @@ public final class Bitmap implements Parcelable {
private static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color);
private static native int nativeRowBytes(long nativeBitmap);
private static native int nativeConfig(long nativeBitmap);
+ private static native boolean nativeIsConfigF16(long nativeBitmap);
private static native int nativeGetPixel(long nativeBitmap, int x, int y);
private static native void nativeGetPixels(long nativeBitmap, int[] pixels,
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4755d45cd434..c9e46942a51a 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1821,6 +1821,8 @@ public abstract class ColorSpace {
* @param cct The correlated color temperature, in Kelvin
* @return Corresponding XYZ values
* @throws IllegalArgumentException If cct is invalid
+ *
+ * @hide
*/
@NonNull
@Size(3)
@@ -1851,6 +1853,8 @@ public abstract class ColorSpace {
* @param srcWhitePoint The white point to adapt from
* @param dstWhitePoint The white point to adapt to
* @return A 3x3 matrix as a non-null array of 9 floats
+ *
+ * @hide
*/
@NonNull
@Size(9)
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3c35d9b33fc8..20303eba6667 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -562,7 +562,7 @@ std::string AssetManager2::GetLastResourceResolution() const {
if (package != nullptr) {
ToResourceName(last_resolution.type_string_ref,
last_resolution.entry_string_ref,
- package,
+ package->GetPackageName(),
&resource_name);
resource_name_string = ToFormattedResourceString(&resource_name);
}
@@ -607,15 +607,25 @@ bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) cons
return false;
}
- const LoadedPackage* package =
- apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
- if (package == nullptr) {
+ const uint8_t package_idx = package_ids_[get_package_id(resid)];
+ if (package_idx == 0xff) {
+ LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
+ get_package_id(resid), resid);
+ return false;
+ }
+
+ const PackageGroup& package_group = package_groups_[package_idx];
+ auto cookie_iter = std::find(package_group.cookies_.begin(),
+ package_group.cookies_.end(), cookie);
+ if (cookie_iter == package_group.cookies_.end()) {
return false;
}
+ long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter);
+ const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_;
return ToResourceName(entry.type_string_ref,
entry.entry_string_ref,
- package,
+ package->GetPackageName(),
out_name);
}
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 645984d85c34..c63dff8f9104 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -48,12 +48,12 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin
!(has_type_separator && out_type->empty());
}
-bool ToResourceName(StringPoolRef& type_string_ref,
- StringPoolRef& entry_string_ref,
- const LoadedPackage* package,
+bool ToResourceName(const StringPoolRef& type_string_ref,
+ const StringPoolRef& entry_string_ref,
+ const StringPiece& package_name,
AssetManager2::ResourceName* out_name) {
- out_name->package = package->GetPackageName().data();
- out_name->package_len = package->GetPackageName().size();
+ out_name->package = package_name.data();
+ out_name->package_len = package_name.size();
out_name->type = type_string_ref.string8(&out_name->type_len);
out_name->type16 = nullptr;
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index eb6eb8e66175..e649940cdde1 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -28,12 +28,11 @@ namespace android {
bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
StringPiece* out_entry);
-// Convert a type_string_ref, entry_string_ref, and package
-// to AssetManager2::ResourceName. Useful for getting
-// resource name without re-running AssetManager2::FindEntry searches.
-bool ToResourceName(StringPoolRef& type_string_ref,
- StringPoolRef& entry_string_ref,
- const LoadedPackage* package,
+// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
+// Useful for getting resource name without re-running AssetManager2::FindEntry searches.
+bool ToResourceName(const StringPoolRef& type_string_ref,
+ const StringPoolRef& entry_string_ref,
+ const StringPiece& package_name,
AssetManager2::ResourceName* out_name);
// Formats a ResourceName to "package:type/entry_name".
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 105dcd209bf7..447fdf5d306a 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -210,6 +210,16 @@ TEST_F(AssetManager2Test, FindsResourceFromAppLoadedAsSharedLibrary) {
EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
}
+TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({lib_one_assets_.get()});
+
+ AssetManager2::ResourceName name;
+ ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name));
+ std::string formatted_name = ToFormattedResourceString(&name);
+ ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo");
+}
+
TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_.get()});
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 56b1885de820..4c675133a6c1 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -62,10 +62,8 @@ DisplayInfo QueryDisplayInfo() {
return displayInfo;
}
-static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut,
- sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
+static void queryWideColorGamutPreference(sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
if (Properties::isolatedProcess) {
- *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
*colorSpace = SkColorSpace::MakeSRGB();
*colorType = SkColorType::kN32_SkColorType;
return;
@@ -78,16 +76,13 @@ static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut,
LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status);
switch (wcgDataspace) {
case ui::Dataspace::DISPLAY_P3:
- *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut;
*colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
break;
case ui::Dataspace::V0_SCRGB:
- *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
*colorSpace = SkColorSpace::MakeSRGB();
break;
case ui::Dataspace::V0_SRGB:
// when sRGB is returned, it means wide color gamut is not supported.
- *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
*colorSpace = SkColorSpace::MakeSRGB();
break;
default:
@@ -112,7 +107,7 @@ DeviceInfo::DeviceInfo() {
mMaxTextureSize = -1;
#endif
mDisplayInfo = QueryDisplayInfo();
- queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType);
+ queryWideColorGamutPreference(&mWideColorSpace, &mWideColorType);
}
int DeviceInfo::maxTextureSize() const {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 9bcc8e8a3dbe..2bab5d3596cf 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -38,7 +38,6 @@ public:
// context or if you are using the HWUI_NULL_GPU
int maxTextureSize() const;
const DisplayInfo& displayInfo() const { return mDisplayInfo; }
- SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; }
sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
SkColorType getWideColorType() const { return mWideColorType; }
@@ -50,7 +49,6 @@ private:
int mMaxTextureSize;
DisplayInfo mDisplayInfo;
- SkColorSpace::Gamut mWideColorGamut;
sk_sp<SkColorSpace> mWideColorSpace;
SkColorType mWideColorType;
};
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 635d0ec66673..39bfcdd944a4 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -164,15 +164,11 @@ static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& sourc
const SkImageInfo& info = source.info();
bitmap.allocPixels(
SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr));
- bitmap.eraseColor(0);
- if (info.colorType() == kRGBA_F16_SkColorType) {
- // Drawing RGBA_F16 onto ARGB_8888 is not supported
- source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()),
- bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
- } else {
- SkCanvas canvas(bitmap);
- canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
- }
+
+ SkCanvas canvas(bitmap);
+ canvas.drawColor(0);
+ canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
+
return bitmap;
}
}
@@ -253,8 +249,8 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou
eglDestroySyncKHR(display, fence);
}
- return Bitmap::createFrom(buffer.get(), bitmap.refColorSpace(), bitmap.alphaType(),
- Bitmap::computePalette(bitmap));
+ return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(),
+ bitmap.alphaType(), Bitmap::computePalette(bitmap));
}
void HardwareBitmapUploader::terminate() {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index cc62fdc76ef8..54a91f4ec06f 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -682,12 +682,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai
float y, float boundsLeft, float boundsTop, float boundsRight,
float boundsBottom, float totalAdvance) {
if (count <= 0 || paint.nothingToDraw()) return;
- SkPaint paintCopy(paint);
+ Paint paintCopy(paint);
if (mPaintFilter) {
- mPaintFilter->filter(&paintCopy);
+ mPaintFilter->filterFullPaint(&paintCopy);
}
- SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
- SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
+ const SkFont& font = paintCopy.getSkFont();
// Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
// older.
if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 &&
@@ -710,12 +709,11 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai
void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) {
- SkPaint paintCopy(paint);
+ Paint paintCopy(paint);
if (mPaintFilter) {
- mPaintFilter->filter(&paintCopy);
+ mPaintFilter->filterFullPaint(&paintCopy);
}
- SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
- SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
+ const SkFont& font = paintCopy.getSkFont();
const int N = end - start;
SkTextBlobBuilder builder;
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 6e0258c9ecb2..3bbee18c6dd1 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -133,12 +133,11 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef)
}
-sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace,
- SkAlphaType alphaType, BitmapPalette palette) {
- // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can
- // view the format as RGBA8888.
+sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, SkColorType colorType,
+ sk_sp<SkColorSpace> colorSpace, SkAlphaType alphaType,
+ BitmapPalette palette) {
SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(),
- kRGBA_8888_SkColorType, alphaType, colorSpace);
+ colorType, alphaType, colorSpace);
return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info, palette));
}
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 2138040d9690..01e45166e0a3 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -72,6 +72,7 @@ public:
* memory that is provided as an input param.
*/
static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer,
+ SkColorType colorType,
sk_sp<SkColorSpace> colorSpace,
SkAlphaType alphaType = kPremul_SkAlphaType,
BitmapPalette palette = BitmapPalette::Unknown);
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 277148e3d556..523148672033 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -39,34 +39,28 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca
}
void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
- uint32_t flags;
- PaintFilter* paintFilter = getPaintFilter();
- if (paintFilter) {
- SkPaint paintCopy(paint);
- paintFilter->filter(&paintCopy);
- flags = paintCopy.getFlags();
- } else {
- flags = paint.getFlags();
- }
- if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) {
+ // paint has already been filtered by our caller, so we can ignore any filter
+ const bool strikeThru = paint.isStrikeThru();
+ const bool underline = paint.isUnderline();
+ if (strikeThru || underline) {
const SkScalar left = x;
const SkScalar right = x + length;
- if (flags & SkPaint::kUnderlineText_ReserveFlag) {
+ const float textSize = paint.getSkFont().getSize();
+ if (underline) {
SkFontMetrics metrics;
- paint.getFontMetrics(&metrics);
+ paint.getSkFont().getMetrics(&metrics);
SkScalar position;
if (!metrics.hasUnderlinePosition(&position)) {
- position = paint.getTextSize() * Paint::kStdUnderline_Top;
+ position = textSize * Paint::kStdUnderline_Top;
}
SkScalar thickness;
if (!metrics.hasUnderlineThickness(&thickness)) {
- thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness;
+ thickness = textSize * Paint::kStdUnderline_Thickness;
}
const SkScalar top = y + position;
drawStroke(left, right, top, thickness, paint, this);
}
- if (flags & SkPaint::kStrikeThruText_ReserveFlag) {
- const float textSize = paint.getTextSize();
+ if (strikeThru) {
const float position = textSize * Paint::kStdStrikeThru_Top;
const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
const SkScalar top = y + position;
@@ -75,19 +69,19 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa
}
}
-static void simplifyPaint(int color, SkPaint* paint) {
+static void simplifyPaint(int color, Paint* paint) {
paint->setColor(color);
paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
- paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
+ paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
paint->setStrokeJoin(SkPaint::kRound_Join);
paint->setLooper(nullptr);
}
class DrawTextFunctor {
public:
- DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x,
+ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
float y, minikin::MinikinRect& bounds, float totalAdvance)
: layout(layout)
, canvas(canvas)
@@ -123,14 +117,14 @@ public:
bool darken = channelSum < (128 * 3);
// outline
- SkPaint outlinePaint(paint);
+ Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop,
bounds.mRight, bounds.mBottom, totalAdvance);
// inner
- SkPaint innerPaint(paint);
+ Paint innerPaint(paint);
simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
innerPaint.setStyle(SkPaint::kFill_Style);
canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop,
@@ -145,7 +139,7 @@ public:
private:
const minikin::Layout& layout;
Canvas* canvas;
- const SkPaint& paint;
+ const Paint& paint;
float x;
float y;
minikin::MinikinRect& bounds;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 84292c8768c1..375f5bc9df37 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -17,8 +17,9 @@
#include "MinikinSkia.h"
#include <SkFontDescriptor.h>
+#include <SkFont.h>
+#include <SkFontMetrics.h>
#include <SkFontMgr.h>
-#include <SkPaint.h>
#include <SkTypeface.h>
#include <log/log.h>
@@ -40,25 +41,24 @@ MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontDat
, mAxes(axes)
, mFilePath(filePath) {}
-static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint,
- const minikin::MinikinPaint& paint,
- const minikin::FontFakery& fakery) {
- skPaint->setTextEncoding(kGlyphID_SkTextEncoding);
- skPaint->setTextSize(paint.size);
- skPaint->setTextScaleX(paint.scaleX);
- skPaint->setTextSkewX(paint.skewX);
- MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags);
+static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont,
+ const minikin::MinikinPaint& paint,
+ const minikin::FontFakery& fakery) {
+ skFont->setSize(paint.size);
+ skFont->setScaleX(paint.scaleX);
+ skFont->setSkewX(paint.skewX);
+ MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags);
// Apply font fakery on top of user-supplied flags.
- MinikinFontSkia::populateSkPaint(skPaint, font, fakery);
+ MinikinFontSkia::populateSkFont(skFont, font, fakery);
}
float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
const minikin::FontFakery& fakery) const {
- SkPaint skPaint;
+ SkFont skFont;
uint16_t glyph16 = glyph_id;
SkScalar skWidth;
- MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
- skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL);
+ MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
+ skFont.getWidths(&glyph16, 1, &skWidth);
#ifdef VERBOSE
ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth);
#endif
@@ -68,11 +68,11 @@ float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::Mi
void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id,
const minikin::MinikinPaint& paint,
const minikin::FontFakery& fakery) const {
- SkPaint skPaint;
+ SkFont skFont;
uint16_t glyph16 = glyph_id;
SkRect skBounds;
- MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
- skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds);
+ MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
+ skFont.getWidths(&glyph16, 1, nullptr, &skBounds);
bounds->mLeft = skBounds.fLeft;
bounds->mTop = skBounds.fTop;
bounds->mRight = skBounds.fRight;
@@ -82,10 +82,10 @@ void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id,
void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent,
const minikin::MinikinPaint& paint,
const minikin::FontFakery& fakery) const {
- SkPaint skPaint;
- MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
+ SkFont skFont;
+ MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
SkFontMetrics metrics;
- skPaint.getFontMetrics(&metrics);
+ skFont.getMetrics(&metrics);
extent->ascent = metrics.fAscent;
extent->descent = metrics.fDescent;
}
@@ -137,28 +137,36 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
ttcIndex, variations);
}
-uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
- uint32_t flags = paint->getFlags();
- unsigned hinting = static_cast<unsigned>(paint->getHinting());
- // select only flags that might affect text layout
- flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
- SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag |
- SkPaint::kAutoHinting_Flag);
- flags |= (hinting << 16);
+// hinting<<16 | edging<<8 | bools:5bits
+uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) {
+ uint32_t flags = (unsigned)font.getHinting() << 16;
+ flags |= (unsigned)font.getEdging() << 8;
+ flags |= font.isEmbolden() << minikin::Embolden_Shift;
+ flags |= font.isLinearMetrics() << minikin::LinearMetrics_Shift;
+ flags |= font.isSubpixel() << minikin::Subpixel_Shift;
+ flags |= font.isEmbeddedBitmaps() << minikin::EmbeddedBitmaps_Shift;
+ flags |= font.isForceAutoHinting() << minikin::ForceAutoHinting_Shift;
return flags;
}
-void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) {
- paint->setFlags(paintFlags & SkPaint::kAllFlags);
- paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16));
+void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) {
+ // We store hinting in the top 16 bits (only need 2 of them)
+ font->setHinting((SkFontHinting)(flags >> 16));
+ // We store edging in bits 8:15 (only need 2 of them)
+ font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF));
+ font->setEmbolden( (flags & minikin::Embolden_Flag) != 0);
+ font->setLinearMetrics( (flags & minikin::LinearMetrics_Flag) != 0);
+ font->setSubpixel( (flags & minikin::Subpixel_Flag) != 0);
+ font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0);
+ font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0);
}
-void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font,
- minikin::FontFakery fakery) {
- paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface());
- paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold());
+void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font,
+ minikin::FontFakery fakery) {
+ skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface());
+ skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold());
if (fakery.isFakeItalic()) {
- paint->setTextSkewX(paint->getTextSkewX() - 0.25f);
+ skFont->setSkewX(skFont->getSkewX() - 0.25f);
}
}
}
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 55576b7bfa4e..ad46b2391cac 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -21,7 +21,7 @@
#include <cutils/compiler.h>
#include <minikin/MinikinFont.h>
-class SkPaint;
+class SkFont;
class SkTypeface;
namespace android {
@@ -54,12 +54,12 @@ public:
std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
const std::vector<minikin::FontVariation>&) const;
- static uint32_t packPaintFlags(const SkPaint* paint);
- static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags);
+ static uint32_t packFontFlags(const SkFont&);
+ static void unpackFontFlags(SkFont*, uint32_t fontFlags);
// set typeface and fake bold/italic parameters
- static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font,
- minikin::FontFakery fakery);
+ static void populateSkFont(SkFont*, const minikin::MinikinFont* font,
+ minikin::FontFakery fakery);
private:
sk_sp<SkTypeface> mTypeface;
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index ba240feb4f41..733f8e415270 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -30,16 +30,17 @@ namespace android {
minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
const Typeface* typeface) {
const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
+ const SkFont& font = paint->getSkFont();
minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection);
/* Prepare minikin Paint */
minikinPaint.size =
- paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize());
- minikinPaint.scaleX = paint->getTextScaleX();
- minikinPaint.skewX = paint->getTextSkewX();
+ font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize());
+ minikinPaint.scaleX = font.getScaleX();
+ minikinPaint.skewX = font.getSkewX();
minikinPaint.letterSpacing = paint->getLetterSpacing();
minikinPaint.wordSpacing = paint->getWordSpacing();
- minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
+ minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
minikinPaint.localeListId = paint->getMinikinLocaleListId();
minikinPaint.familyVariant = paint->getFamilyVariant();
minikinPaint.fontStyle = resolvedFace->fStyle;
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index d27d54454ea0..cbf409504675 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -63,27 +63,29 @@ public:
// f is a functor of type void f(size_t start, size_t end);
template <typename F>
ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
- float saveSkewX = paint->getTextSkewX();
- bool savefakeBold = paint->isFakeBoldText();
+ float saveSkewX = paint->getSkFont().getSkewX();
+ bool savefakeBold = paint->getSkFont().isEmbolden();
const minikin::MinikinFont* curFont = nullptr;
size_t start = 0;
size_t nGlyphs = layout.nGlyphs();
for (size_t i = 0; i < nGlyphs; i++) {
const minikin::MinikinFont* nextFont = layout.getFont(i);
if (i > 0 && nextFont != curFont) {
- MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
f(start, i);
- paint->setTextSkewX(saveSkewX);
- paint->setFakeBoldText(savefakeBold);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
start = i;
}
curFont = nextFont;
}
if (nGlyphs > start) {
- MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
f(start, nGlyphs);
- paint->setTextSkewX(saveSkewX);
- paint->setFakeBoldText(savefakeBold);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
}
}
};
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 92ffda9486bb..601b3c23cc1f 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -21,6 +21,7 @@
#include <cutils/compiler.h>
+#include <SkFont.h>
#include <SkPaint.h>
#include <string>
@@ -46,7 +47,6 @@ public:
Paint();
Paint(const Paint& paint);
- Paint(const SkPaint& paint); // NOLINT(google-explicit-constructor)
~Paint();
Paint& operator=(const Paint& other);
@@ -54,6 +54,17 @@ public:
friend bool operator==(const Paint& a, const Paint& b);
friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); }
+ SkFont& getSkFont() { return mFont; }
+ const SkFont& getSkFont() const { return mFont; }
+
+ // These shadow the methods on SkPaint, but we need to so we can keep related
+ // attributes in-sync.
+
+ void reset();
+ void setAntiAlias(bool);
+
+ // End method shadowing
+
void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; }
float getLetterSpacing() const { return mLetterSpacing; }
@@ -94,7 +105,31 @@ public:
Align getTextAlign() const { return mAlign; }
void setTextAlign(Align align) { mAlign = align; }
+ bool isStrikeThru() const { return mStrikeThru; }
+ void setStrikeThru(bool st) { mStrikeThru = st; }
+
+ bool isUnderline() const { return mUnderline; }
+ void setUnderline(bool u) { mUnderline = u; }
+
+ bool isDevKern() const { return mDevKern; }
+ void setDevKern(bool d) { mDevKern = d; }
+
+ // The Java flags (Paint.java) no longer fit into the native apis directly.
+ // These methods handle converting to and from them and the native representations
+ // in android::Paint.
+
+ uint32_t getJavaFlags() const;
+ void setJavaFlags(uint32_t);
+
+ // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags
+ // that are meant for SkFont or Paint (e.g. underline, strikethru)
+ // The only respected flags are : [ antialias, dither, filterBitmap ]
+ static uint32_t GetSkPaintJavaFlags(const SkPaint&);
+ static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
+
private:
+ SkFont mFont;
+
float mLetterSpacing = 0;
float mWordSpacing = 0;
std::string mFontFeatureSettings;
@@ -107,6 +142,9 @@ private:
// nullptr is valid: it means the default typeface.
const Typeface* mTypeface = nullptr;
Align mAlign = kLeft_Align;
+ bool mStrikeThru = false;
+ bool mUnderline = false;
+ bool mDevKern = false;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
index bf5627eac229..0e7b61977000 100644
--- a/libs/hwui/hwui/PaintFilter.h
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -12,6 +12,7 @@ public:
* The implementation may modify the paint as they wish.
*/
virtual void filter(SkPaint*) = 0;
+ virtual void filterFullPaint(Paint*) = 0;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index bdbf5cacaaf0..2f2d575bca29 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,10 +24,16 @@ Paint::Paint()
, mWordSpacing(0)
, mFontFeatureSettings()
, mMinikinLocaleListId(0)
- , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {}
+ , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
+ // SkPaint::antialiasing defaults to false, but
+ // SkFont::edging defaults to kAntiAlias. To keep them
+ // insync, we manually set the font to kAilas.
+ mFont.setEdging(SkFont::Edging::kAlias);
+}
Paint::Paint(const Paint& paint)
: SkPaint(paint)
+ , mFont(paint.mFont)
, mLetterSpacing(paint.mLetterSpacing)
, mWordSpacing(paint.mWordSpacing)
, mFontFeatureSettings(paint.mFontFeatureSettings)
@@ -35,20 +41,17 @@ Paint::Paint(const Paint& paint)
, mFamilyVariant(paint.mFamilyVariant)
, mHyphenEdit(paint.mHyphenEdit)
, mTypeface(paint.mTypeface)
- , mAlign(paint.mAlign) {}
+ , mAlign(paint.mAlign)
+ , mStrikeThru(paint.mStrikeThru)
+ , mUnderline(paint.mUnderline)
+ , mDevKern(paint.mDevKern) {}
-Paint::Paint(const SkPaint& paint)
- : SkPaint(paint)
- , mLetterSpacing(0)
- , mWordSpacing(0)
- , mFontFeatureSettings()
- , mMinikinLocaleListId(0)
- , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {}
Paint::~Paint() {}
Paint& Paint::operator=(const Paint& other) {
SkPaint::operator=(other);
+ mFont = other.mFont;
mLetterSpacing = other.mLetterSpacing;
mWordSpacing = other.mWordSpacing;
mFontFeatureSettings = other.mFontFeatureSettings;
@@ -57,15 +60,136 @@ Paint& Paint::operator=(const Paint& other) {
mHyphenEdit = other.mHyphenEdit;
mTypeface = other.mTypeface;
mAlign = other.mAlign;
+ mStrikeThru = other.mStrikeThru;
+ mUnderline = other.mUnderline;
+ mDevKern = other.mDevKern;
return *this;
}
bool operator==(const Paint& a, const Paint& b) {
return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
+ a.mFont == b.mFont &&
a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
a.mFontFeatureSettings == b.mFontFeatureSettings &&
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
- a.mTypeface == b.mTypeface && a.mAlign == b.mAlign;
+ a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
+ a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
+ a.mDevKern == b.mDevKern;
+}
+
+void Paint::reset() {
+ SkPaint::reset();
+
+ mFont = SkFont();
+ mFont.setEdging(SkFont::Edging::kAlias);
+
+ mStrikeThru = false;
+ mUnderline = false;
+ mDevKern = false;
+}
+
+void Paint::setAntiAlias(bool aa) {
+ // Java does not support/understand subpixel(lcd) antialiasing
+ SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias);
+ // JavaPaint antialiasing affects both the SkPaint and SkFont settings.
+ SkPaint::setAntiAlias(aa);
+ mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
+}
+
+////////////////// Java flags compatibility //////////////////
+
+/* Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
+ match up with skia anymore, so we have to do some shuffling in get/set flags()
+
+ 3 flags apply to SkPaint (antialias, dither, filter -> enum)
+ 5 flags (merged with antialias) are for SkFont
+ 2 flags are for minikin::Paint (underline and strikethru)
+*/
+
+// flags relating to SkPaint
+static const uint32_t sAntiAliasFlag = 0x01; // affects paint and font-edging
+static const uint32_t sFilterBitmapFlag = 0x02; // maps to enum
+static const uint32_t sDitherFlag = 0x04;
+// flags relating to SkFont
+static const uint32_t sFakeBoldFlag = 0x020;
+static const uint32_t sLinearMetrics = 0x040;
+static const uint32_t sSubpixelMetrics = 0x080;
+static const uint32_t sEmbeddedBitmaps = 0x400;
+static const uint32_t sForceAutoHinting = 0x800;
+// flags related to minikin::Paint
+static const uint32_t sUnderlineFlag = 0x08;
+static const uint32_t sStrikeThruFlag = 0x10;
+// flags no longer supported on native side (but mirrored for compatibility)
+static const uint32_t sDevKernFlag = 0x100;
+
+static uint32_t paintToLegacyFlags(const SkPaint& paint) {
+ uint32_t flags = 0;
+ flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag;
+ flags |= -(int)paint.isDither() & sDitherFlag;
+ if (paint.getFilterQuality() != kNone_SkFilterQuality) {
+ flags |= sFilterBitmapFlag;
+ }
+ return flags;
}
+
+static uint32_t fontToLegacyFlags(const SkFont& font) {
+ uint32_t flags = 0;
+ flags |= -(int)font.isEmbolden() & sFakeBoldFlag;
+ flags |= -(int)font.isLinearMetrics() & sLinearMetrics;
+ flags |= -(int)font.isSubpixel() & sSubpixelMetrics;
+ flags |= -(int)font.isEmbeddedBitmaps() & sEmbeddedBitmaps;
+ flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting;
+ return flags;
+}
+
+static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) {
+ paint->setAntiAlias((flags & sAntiAliasFlag) != 0);
+ paint->setDither ((flags & sDitherFlag) != 0);
+
+ if (flags & sFilterBitmapFlag) {
+ paint->setFilterQuality(kLow_SkFilterQuality);
+ } else {
+ paint->setFilterQuality(kNone_SkFilterQuality);
+ }
+}
+
+static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) {
+ font->setEmbolden ((flags & sFakeBoldFlag) != 0);
+ font->setLinearMetrics ((flags & sLinearMetrics) != 0);
+ font->setSubpixel ((flags & sSubpixelMetrics) != 0);
+ font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0);
+ font->setForceAutoHinting((flags & sForceAutoHinting) != 0);
+
+ if (flags & sAntiAliasFlag) {
+ font->setEdging(SkFont::Edging::kAntiAlias);
+ } else {
+ font->setEdging(SkFont::Edging::kAlias);
+ }
+}
+
+uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) {
+ return paintToLegacyFlags(paint);
+}
+
+void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) {
+ applyLegacyFlagsToPaint(flags, paint);
+}
+
+uint32_t Paint::getJavaFlags() const {
+ uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont);
+ flags |= -(int)mStrikeThru & sStrikeThruFlag;
+ flags |= -(int)mUnderline & sUnderlineFlag;
+ flags |= -(int)mDevKern & sDevKernFlag;
+ return flags;
+}
+
+void Paint::setJavaFlags(uint32_t flags) {
+ applyLegacyFlagsToPaint(flags, this);
+ applyLegacyFlagsToFont(flags, &mFont);
+ mStrikeThru = (flags & sStrikeThruFlag) != 0;
+ mUnderline = (flags & sUnderlineFlag) != 0;
+ mDevKern = (flags & sDevKernFlag) != 0;
+}
+
} // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 60c805741058..a1b2b18195bc 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -138,6 +138,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
info.width = fboSize.width();
info.height = fboSize.height();
mat4.asColMajorf(&info.transform[0]);
+ info.color_space_ptr = canvas->imageInfo().colorSpace();
// ensure that the framebuffer that the webview will render into is bound before we clear
// the stencil and/or draw the functor.
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 1661905eff57..8508274676fd 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -31,8 +31,8 @@ namespace skiapipeline {
// Cache size limits.
static const size_t maxKeySize = 1024;
-static const size_t maxValueSize = 64 * 1024;
-static const size_t maxTotalSize = 512 * 1024;
+static const size_t maxValueSize = 512 * 1024;
+static const size_t maxTotalSize = 1024 * 1024;
ShaderCache::ShaderCache() {
// There is an "incomplete FileBlobCache type" compilation error, if ctor is moved to header.
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cfbb9956d3dc..570e895a012d 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -167,7 +167,7 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
if (surface) {
mRenderThread.requireGlContext();
- auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut);
+ auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace);
if (!newSurface) {
return false;
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 47c90948bbbe..a00a36f93501 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -476,11 +476,9 @@ void SkiaPipeline::dumpResourceCacheUsage() const {
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
if (colorMode == ColorMode::SRGB) {
mSurfaceColorType = SkColorType::kN32_SkColorType;
- mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
mSurfaceColorSpace = SkColorSpace::MakeSRGB();
} else if (colorMode == ColorMode::WideColorGamut) {
mSurfaceColorType = DeviceInfo::get()->getWideColorType();
- mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut();
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
} else {
LOG_ALWAYS_FATAL("Unreachable: unsupported color mode.");
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index e9957df95f10..7381e0417a2d 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -116,7 +116,6 @@ protected:
renderthread::RenderThread& mRenderThread;
SkColorType mSurfaceColorType;
- SkColorSpace::Gamut mSurfaceColorGamut;
sk_sp<SkColorSpace> mSurfaceColorSpace;
private:
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 53495a7d62c0..d0fe022616c5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -129,7 +129,7 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
setSurfaceColorProperties(colorMode);
if (surface) {
mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace,
- mSurfaceColorGamut, mSurfaceColorType);
+ mSurfaceColorType);
}
return mVkSurface != nullptr;
@@ -149,20 +149,8 @@ void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* func
sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
SkBitmap& skBitmap) {
- // TODO: implement this function for Vulkan pipeline
- // code below is a hack to avoid crashing because of missing HW Bitmap support
- sp<GraphicBuffer> buffer = new GraphicBuffer(
- skBitmap.info().width(), skBitmap.info().height(), PIXEL_FORMAT_RGBA_8888,
- GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
- GraphicBuffer::USAGE_SW_READ_NEVER,
- std::string("SkiaVulkanPipeline::allocateHardwareBitmap pid [") +
- std::to_string(getpid()) + "]");
- status_t error = buffer->initCheck();
- if (error < 0) {
- ALOGW("SkiaVulkanPipeline::allocateHardwareBitmap() failed in GraphicBuffer.create()");
- return nullptr;
- }
- return Bitmap::createFrom(buffer, skBitmap.refColorSpace());
+ LOG_ALWAYS_FATAL("Unimplemented");
+ return nullptr;
}
} /* namespace skiapipeline */
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index c3563dbfd3cd..706325f00bd2 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -132,6 +132,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) {
info.width = mFBInfo.width();
info.height = mFBInfo.height();
mat4.asColMajorf(&info.transform[0]);
+ info.color_space_ptr = canvas->imageInfo().colorSpace();
glViewport(0, 0, info.width, info.height);
@@ -179,8 +180,8 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) {
canvas->resetMatrix();
auto functorImage = SkImage::MakeFromAHardwareBuffer(
- reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr,
- kBottomLeft_GrSurfaceOrigin);
+ reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType,
+ canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin);
canvas->drawImage(functorImage, 0, 0, &paint);
canvas->restore();
}
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index 9e1bb8e8e548..501b8df9bc36 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_HWUI_DRAW_GL_INFO_H
#define ANDROID_HWUI_DRAW_GL_INFO_H
+#include <SkColorSpace.h>
+
namespace android {
namespace uirenderer {
@@ -41,6 +43,9 @@ struct DrawGlInfo {
// Input: current transform matrix, in OpenGL format
float transform[16];
+ // Input: Color space.
+ const SkColorSpace* color_space_ptr;
+
// Output: dirty region to redraw
float dirtyLeft;
float dirtyTop;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 8cd97ed20215..2cc3f362e172 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -132,11 +132,13 @@ void EglManager::initialize() {
createPBufferSurface();
makeCurrent(mPBufferSurface, nullptr, /* force */ true);
- SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut();
+ skcms_Matrix3x3 wideColorGamut;
+ LOG_ALWAYS_FATAL_IF(!DeviceInfo::get()->getWideColorSpace()->toXYZD50(&wideColorGamut),
+ "Could not get gamut matrix from wideColorSpace");
bool hasWideColorSpaceExtension = false;
- if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+ if (memcmp(&wideColorGamut, &SkNamedGamut::kDCIP3, sizeof(wideColorGamut)) == 0) {
hasWideColorSpaceExtension = EglExtensions.displayP3;
- } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+ } else if (memcmp(&wideColorGamut, &SkNamedGamut::kSRGB, sizeof(wideColorGamut)) == 0) {
hasWideColorSpaceExtension = EglExtensions.scRGB;
} else {
LOG_ALWAYS_FATAL("Unsupported wide color space.");
@@ -297,7 +299,7 @@ void EglManager::createPBufferSurface() {
Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
ColorMode colorMode,
- SkColorSpace::Gamut colorGamut) {
+ sk_sp<SkColorSpace> colorSpace) {
LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport &&
@@ -330,15 +332,15 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
if (EglExtensions.glColorSpace) {
attribs[0] = EGL_GL_COLORSPACE_KHR;
if (wideColorGamut) {
- switch (colorGamut) {
- case SkColorSpace::Gamut::kDCIP3_D65_Gamut:
- attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
- break;
- case SkColorSpace::Gamut::kSRGB_Gamut:
- attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
- break;
- default:
- LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+ skcms_Matrix3x3 colorGamut;
+ LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
+ "Could not get gamut matrix from color space");
+ if (memcmp(&colorGamut, &SkNamedGamut::kDCIP3, sizeof(colorGamut)) == 0) {
+ attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ } else {
+ LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
}
} else {
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 4dd90961b4f7..27d41d26a73a 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -49,7 +49,7 @@ public:
bool hasEglContext();
Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode,
- SkColorSpace::Gamut colorGamut);
+ sk_sp<SkColorSpace> colorSpace);
void destroySurface(EGLSurface surface);
void destroy();
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 5c6cb9ad43db..1e7520216d66 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -516,10 +516,9 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) {
if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) {
ColorMode colorMode = surface->mColorMode;
sk_sp<SkColorSpace> colorSpace = surface->mColorSpace;
- SkColorSpace::Gamut colorGamut = surface->mColorGamut;
SkColorType colorType = surface->mColorType;
destroySurface(surface);
- *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType);
+ *surfaceOut = createSurface(window, colorMode, colorSpace, colorType);
surface = *surfaceOut;
}
@@ -841,9 +840,12 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) {
}
if (surface->mColorMode == ColorMode::WideColorGamut) {
- if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+ skcms_Matrix3x3 surfaceGamut;
+ LOG_ALWAYS_FATAL_IF(!surface->mColorSpace->toXYZD50(&surfaceGamut),
+ "Could not get gamut matrix from color space");
+ if (memcmp(&surfaceGamut, &SkNamedGamut::kSRGB, sizeof(surfaceGamut)) == 0) {
colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
- } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+ } else if (memcmp(&surfaceGamut, &SkNamedGamut::kDCIP3, sizeof(surfaceGamut)) == 0) {
colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT;
} else {
LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
@@ -922,7 +924,6 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) {
VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode,
sk_sp<SkColorSpace> surfaceColorSpace,
- SkColorSpace::Gamut surfaceColorGamut,
SkColorType surfaceColorType) {
initialize();
@@ -931,7 +932,7 @@ VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode col
}
VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace,
- surfaceColorGamut, surfaceColorType);
+ surfaceColorType);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b06eb82dac79..abe78efc9174 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -39,9 +39,9 @@ class RenderThread;
class VulkanSurface {
public:
VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace,
- SkColorSpace::Gamut colorGamut, SkColorType colorType)
+ SkColorType colorType)
: mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace),
- mColorGamut(colorGamut), mColorType(colorType) {}
+ mColorType(colorType) {}
sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; }
@@ -90,7 +90,6 @@ private:
int mWindowWidth = 0;
int mWindowHeight = 0;
sk_sp<SkColorSpace> mColorSpace;
- SkColorSpace::Gamut mColorGamut;
SkColorType mColorType;
VkSurfaceTransformFlagBitsKHR mTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
SkMatrix mPreTransform;
@@ -113,7 +112,6 @@ public:
// VulkanSurface object which is returned.
VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode,
sk_sp<SkColorSpace> surfaceColorSpace,
- SkColorSpace::Gamut surfaceColorGamut,
SkColorType surfaceColorType);
// Destroy the VulkanSurface and all associated vulkan objects.
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 16a27598c56a..a9f651d38a06 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -78,24 +78,21 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater(
return layerUpdater;
}
-void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x,
float y) {
auto utf16 = asciiToUtf16(text);
uint32_t length = strlen(text);
- Paint glyphPaint(paint);
- glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
+
canvas->drawText(utf16.get(), length, // text buffer
0, length, // draw range
0, length, // context range
- x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */);
+ x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */);
}
-void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint,
const SkPath& path) {
auto utf16 = asciiToUtf16(text);
- Paint glyphPaint(paint);
- glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
- canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
+ canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
nullptr);
}
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 6a1ca5a25361..e7124df72beb 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -278,10 +278,10 @@ public:
static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
- static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
+ static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x,
float y);
- static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
+ static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint,
const SkPath& path);
static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str);
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index f0a5e9dff1b9..0795d13f441b 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -51,7 +51,7 @@ public:
paint.setAntiAlias(true);
paint.setColor(Color::Black);
for (int i = 0; i < 5; i++) {
- paint.setTextSize(10 + (frameNr % 20) + i * 20);
+ paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20);
TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2));
}
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index ec81f629ee45..2af955fbb711 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -50,7 +50,8 @@ public:
pixels[4000 + 4 * i + 3] = 255;
}
buffer->unlock();
- sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, SkColorSpace::MakeSRGB()));
+ sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer, kRGBA_8888_SkColorType,
+ SkColorSpace::MakeSRGB()));
sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
SkPoint center;
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 58c99800875b..ecaaf487e4f8 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -16,7 +16,7 @@
#include "TestSceneBase.h"
#include "tests/common/TestListViewSceneBase.h"
-
+#include "hwui/Paint.h"
#include <SkGradientShader.h>
class ListOfFadedTextAnimation;
@@ -33,8 +33,8 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase {
canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
int length = dp(100);
canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer);
- SkPaint textPaint;
- textPaint.setTextSize(dp(20));
+ Paint textPaint;
+ textPaint.getSkFont().setSize(dp(20));
textPaint.setAntiAlias(true);
TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4111bd24847e..feb881f654f8 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -16,6 +16,7 @@
#include "TestSceneBase.h"
#include "tests/common/TestListViewSceneBase.h"
+#include "hwui/Paint.h"
#include <SkFont.h>
#include <cstdio>
@@ -83,14 +84,14 @@ class ListViewAnimation : public TestListViewSceneBase {
roundRectPaint.setColor(Color::White);
canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
- SkPaint textPaint;
+ Paint textPaint;
textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
- textPaint.setTextSize(dp(20));
+ textPaint.getSkFont().setSize(dp(20));
textPaint.setAntiAlias(true);
char buf[256];
snprintf(buf, sizeof(buf), "This card is #%d", cardId);
TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25));
- textPaint.setTextSize(dp(15));
+ textPaint.getSkFont().setSize(dp(15));
TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
itemHeight, dp(45));
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index aa537b4f329c..f6cff1c643a1 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -17,6 +17,7 @@
#include "TestSceneBase.h"
#include "renderthread/RenderProxy.h"
#include "utils/Color.h"
+#include "hwui/Paint.h"
class MagnifierAnimation;
@@ -37,9 +38,9 @@ public:
canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
card = TestUtils::createNode(
0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
- SkPaint paint;
+ Paint paint;
paint.setAntiAlias(true);
- paint.setTextSize(50);
+ paint.getSkFont().setSize(50);
paint.setColor(Color::Black);
TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400);
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index 3befce4a395f..8630be87c09c 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -41,9 +41,9 @@ public:
int top = bounds.fTop;
mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
- mBluePaint.setTextSize(padding);
+ mBluePaint.getSkFont().setSize(padding);
mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
- mGreenPaint.setTextSize(padding);
+ mGreenPaint.getSkFont().setSize(padding);
// interleave drawText and drawRect with saveLayer ops
for (int i = 0; i < regions; i++, top += smallRectHeight) {
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index a16b17849fc6..d30903679bce 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -15,6 +15,7 @@
*/
#include "TestSceneBase.h"
+#include "hwui/Paint.h"
class TextAnimation;
@@ -28,9 +29,9 @@ public:
canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props,
Canvas& canvas) {
- SkPaint paint;
+ Paint paint;
paint.setAntiAlias(true);
- paint.setTextSize(50);
+ paint.getSkFont().setSize(50);
paint.setColor(Color::Black);
for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index 286f5f194aed..229c7f392629 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -17,6 +17,7 @@
#include "SkBlendMode.h"
#include "TestSceneBase.h"
#include "tests/common/BitmapAllocationTestUtils.h"
+#include "hwui/Paint.h"
class TvApp;
class TvAppNoRoundedCorner;
@@ -116,13 +117,13 @@ private:
[text, text2](RenderProperties& props, Canvas& canvas) {
canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
- SkPaint paint;
+ Paint paint;
paint.setAntiAlias(true);
- paint.setTextSize(24);
+ paint.getSkFont().setSize(24);
paint.setColor(Color::Black);
TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30);
- paint.setTextSize(20);
+ paint.getSkFont().setSize(20);
TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54);
});
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 4415a593f6ee..d14116f7b555 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -45,6 +45,20 @@ android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType) {
}
}
+SkColorType PixelFormatToColorType(android::PixelFormat format) {
+ switch (format) {
+ case PIXEL_FORMAT_RGBX_8888: return kRGB_888x_SkColorType;
+ case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_SkColorType;
+ case PIXEL_FORMAT_RGBA_FP16: return kRGBA_F16_SkColorType;
+ case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType;
+ case PIXEL_FORMAT_RGBA_1010102: return kRGBA_1010102_SkColorType;
+ case PIXEL_FORMAT_RGBA_4444: return kARGB_4444_SkColorType;
+ default:
+ ALOGW("Unsupported PixelFormat: %d, return kUnknown_SkColorType by default", format);
+ return kUnknown_SkColorType;
+ }
+}
+
sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
skcms_Matrix3x3 gamut;
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index 388025207ed6..b67d10d4249c 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -112,6 +112,7 @@ static constexpr float EOCF(float srgb) {
}
android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType);
+ANDROID_API SkColorType PixelFormatToColorType(android::PixelFormat format);
ANDROID_API sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace);
diff --git a/media/Android.bp b/media/Android.bp
index 88ed9c6a05a9..0675a36f3e81 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -132,7 +132,6 @@ filegroup {
"apex/java/android/media/Session2Command.java",
"apex/java/android/media/Session2CommandGroup.java",
"apex/java/android/media/Session2Link.java",
- "apex/java/android/media/Session2Token.java",
],
}
diff --git a/media/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java
index 65b6f55a068a..45ea8261c6fb 100644
--- a/media/apex/java/android/media/MediaConstants.java
+++ b/media/apex/java/android/media/MediaConstants.java
@@ -24,7 +24,8 @@ class MediaConstants {
static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME";
// Bundle key for Parcelable
- static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK";
+ static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN";
+ static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK";
static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS";
static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE";
diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
index 887b4475a4d1..41721f3011b1 100644
--- a/media/apex/java/android/media/MediaController2.java
+++ b/media/apex/java/android/media/MediaController2.java
@@ -20,9 +20,11 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
import static android.media.MediaConstants.KEY_PACKAGE_NAME;
import static android.media.MediaConstants.KEY_PID;
import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
+import static android.media.MediaConstants.KEY_SESSION2_LINK;
+import static android.media.MediaConstants.KEY_SESSION2_TOKEN;
import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR;
import static android.media.Session2Command.RESULT_INFO_SKIPPED;
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
import static android.media.Session2Token.TYPE_SESSION;
import android.annotation.NonNull;
@@ -260,7 +262,8 @@ public class MediaController2 implements AutoCloseable {
// Called by Controller2Link.onConnected
void onConnected(int seq, Bundle connectionResult) {
- Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK);
+ Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN);
+ Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK);
Session2CommandGroup allowedCommands =
connectionResult.getParcelable(KEY_ALLOWED_COMMANDS);
boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE);
@@ -281,8 +284,7 @@ public class MediaController2 implements AutoCloseable {
// Implementation for the local binder is no-op,
// so can be used without worrying about deadlock.
sessionBinder.linkToDeath(mDeathRecipient, 0);
- mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION,
- mSessionToken.getPackageName(), sessionBinder);
+ mConnectedToken = token;
}
mCallbackExecutor.execute(() -> {
mCallback.onConnected(MediaController2.this, allowedCommands);
@@ -353,7 +355,7 @@ public class MediaController2 implements AutoCloseable {
}
private boolean requestConnectToSession() {
- Session2Link sessionBinder = mSessionToken.getSessionLink();
+ Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK);
Bundle connectionRequest = createConnectionRequest();
try {
sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
@@ -366,7 +368,7 @@ public class MediaController2 implements AutoCloseable {
private boolean requestConnectToService() {
// Service. Needs to get fresh binder whenever connection is needed.
- final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
+ final Intent intent = new Intent(SESSION_SERVICE_INTERFACE);
intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName());
// Use bindService() instead of startForegroundService() to start session service for three
diff --git a/media/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
index fdd07fdd52e3..80c91cc79c78 100644
--- a/media/apex/java/android/media/MediaSession2.java
+++ b/media/apex/java/android/media/MediaSession2.java
@@ -20,10 +20,10 @@ import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
import static android.media.MediaConstants.KEY_PACKAGE_NAME;
import static android.media.MediaConstants.KEY_PID;
import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
+import static android.media.MediaConstants.KEY_SESSION2_LINK;
+import static android.media.MediaConstants.KEY_SESSION2_TOKEN;
import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR;
import static android.media.Session2Command.RESULT_INFO_SKIPPED;
-import static android.media.Session2Token.TYPE_SESSION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,7 +34,6 @@ import android.media.session.MediaSessionManager;
import android.media.session.MediaSessionManager.RemoteUserInfo;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Process;
import android.os.ResultReceiver;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -108,8 +107,10 @@ public class MediaSession2 implements AutoCloseable {
mCallbackExecutor = callbackExecutor;
mCallback = callback;
mSessionStub = new Session2Link(this);
- mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(),
- mSessionStub);
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(KEY_SESSION2_LINK, mSessionStub);
+ mSessionToken = new Session2Token(context, id, extras);
mSessionManager = (MediaSessionManager) mContext.getSystemService(
Context.MEDIA_SESSION_SERVICE);
// NOTE: mResultHandler uses main looper, so this MUST NOT be blocked.
@@ -141,6 +142,8 @@ public class MediaSession2 implements AutoCloseable {
for (ControllerInfo info : controllerInfos) {
info.notifyDisconnected();
}
+ mSessionToken.destroy();
+ mSessionManager.notifySession2Destroyed(mSessionToken);
} catch (Exception e) {
// Should not be here.
}
@@ -328,7 +331,7 @@ public class MediaSession2 implements AutoCloseable {
// It's needed because we cannot call synchronous calls between
// session/controller.
Bundle connectionResult = new Bundle();
- connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub);
+ connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken);
connectionResult.putParcelable(KEY_ALLOWED_COMMANDS,
controllerInfo.mAllowedCommands);
connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive());
diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
index 5bb746a7f9e3..f18cd317ef12 100644
--- a/media/apex/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -16,6 +16,8 @@
package android.media;
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
+
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -45,10 +47,6 @@ import java.util.Map;
* for consistent behavior across all devices.
*/
public abstract class MediaSession2Service extends Service {
- /**
- * The {@link Intent} that must be declared as handled by the service.
- */
- public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
private static final String TAG = "MediaSession2Service";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -100,7 +98,7 @@ public abstract class MediaSession2Service extends Service {
@Override
@Nullable
public IBinder onBind(@NonNull Intent intent) {
- if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ if (SESSION_SERVICE_INTERFACE.equals(intent.getAction())) {
synchronized (mLock) {
return mStub;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b7f042b48da1..f996d38c86a9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -30,6 +30,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -3989,33 +3990,11 @@ public class AudioManager {
}
/**
- * Indicate A2DP source or sink connection state change.
- * @param device Bluetooth device connected/disconnected
- * @param state new connection state (BluetoothProfile.STATE_xxx)
- * @param profile profile for the A2DP device
- * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
- * {@link android.bluetooth.BluetoothProfile.A2DP_SINK})
- * @return a delay in ms that the caller should wait before broadcasting
- * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent.
- * {@hide}
- */
- public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state,
- int profile) {
- final IAudioService service = getService();
- int delay = 0;
- try {
- delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return delay;
- }
-
- /**
* Indicate A2DP source or sink connection state change and eventually suppress
* the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
* @param device Bluetooth device connected/disconnected
- * @param state new connection state (BluetoothProfile.STATE_xxx)
+ * @param state new connection state, {@link BluetoothProfile#STATE_CONNECTED}
+ * or {@link BluetoothProfile#STATE_DISCONNECTED}
* @param profile profile for the A2DP device
* @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting.
* (either {@link android.bluetooth.BluetoothProfile.A2DP} or
@@ -4027,8 +4006,8 @@ public class AudioManager {
* {@hide}
*/
public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- BluetoothDevice device, int state, int profile,
- boolean suppressNoisyIntent, int a2dpVolume) {
+ BluetoothDevice device, int state,
+ int profile, boolean suppressNoisyIntent, int a2dpVolume) {
final IAudioService service = getService();
int delay = 0;
try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af016d5d4be9..ffa3b247480a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -39,6 +39,8 @@ import java.util.Map;
*/
public class AudioSystem
{
+ private static final boolean DEBUG_VOLUME = true;
+
private static final String TAG = "AudioSystem";
/* These values must be kept in sync with system/audio.h */
/*
@@ -879,6 +881,15 @@ public class AudioSystem
}
}
+ /** Wrapper for native methods called from AudioService */
+ public static int setStreamVolumeIndexAS(int stream, int index, int device) {
+ if (DEBUG_VOLUME) {
+ Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]
+ + " dev=" + Integer.toHexString(device) + " idx=" + index);
+ }
+ return setStreamVolumeIndex(stream, index, device);
+ }
+
// usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
public static final int SYNC_EVENT_NONE = 0;
public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
@@ -906,7 +917,7 @@ public class AudioSystem
@UnsupportedAppUsage
public static native int initStreamVolume(int stream, int indexMin, int indexMax);
@UnsupportedAppUsage
- public static native int setStreamVolumeIndex(int stream, int index, int device);
+ private static native int setStreamVolumeIndex(int stream, int index, int device);
public static native int getStreamVolumeIndex(int stream, int device);
public static native int setMasterVolume(float value);
public static native float getMasterVolume();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 14bdab98d46b..f5aeca717424 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -151,8 +151,6 @@ interface IAudioService {
void setWiredDeviceConnectionState(int type, int state, String address, String name,
String caller);
- int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
-
void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device);
int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device,
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 4eed12f428bc..33e7d8ea1b94 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -335,7 +335,7 @@ public class MediaScanner implements AutoCloseable {
private final Uri mPlaylistsUri;
@UnsupportedAppUsage
private final Uri mFilesUri;
- private final Uri mFilesUriNoNotify;
+ private final Uri mFilesFullUri;
private final boolean mProcessPlaylists;
private final boolean mProcessGenres;
private int mMtpObjectHandle;
@@ -445,7 +445,11 @@ public class MediaScanner implements AutoCloseable {
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
mFilesUri = Files.getContentUri(volumeName);
- mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+
+ Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+ filesFullUri = MediaStore.setIncludePending(filesFullUri);
+ filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
+ mFilesFullUri = filesFullUri;
if (!volumeName.equals("internal")) {
// we only support playlists on external media
@@ -1625,7 +1629,7 @@ public class MediaScanner implements AutoCloseable {
try {
where = Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { path };
- c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
+ c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, null, null);
if (c != null && c.moveToFirst()) {
long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
diff --git a/media/apex/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
index 238cc2b8ee7d..80494ad1b2a6 100644
--- a/media/apex/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -19,13 +19,17 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.media.session.MediaSessionManager;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
@@ -35,7 +39,7 @@ import java.util.List;
import java.util.Objects;
/**
- * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
+ * Represents an ongoing MediaSession2 or a MediaSession2Service.
* If it's representing a session service, it may not be ongoing.
* <p>
* This API is not generally intended for third party application developers.
@@ -44,7 +48,7 @@ import java.util.Objects;
* for consistent behavior across all devices.
* <p>
* This may be passed to apps by the session owner to allow them to create a
- * {@link MediaController2} to communicate with the session.
+ * MediaController2 to communicate with the session.
* <p>
* It can be also obtained by {@link android.media.session.MediaSessionManager}.
*/
@@ -64,6 +68,13 @@ public final class Session2Token implements Parcelable {
};
/**
+ * The {@link Intent} that must be declared for the session service.
+ * @hide
+ */
+ @SystemApi
+ public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service";
+
+ /**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -72,22 +83,26 @@ public final class Session2Token implements Parcelable {
}
/**
- * Type for {@link MediaSession2}.
+ * Type for MediaSession2.
*/
public static final int TYPE_SESSION = 0;
/**
- * Type for {@link MediaSession2Service}.
+ * Type for MediaSession2Service.
*/
public static final int TYPE_SESSION_SERVICE = 1;
+ private final String mSessionId;
+ private final int mPid;
private final int mUid;
@TokenType
private final int mType;
private final String mPackageName;
private final String mServiceName;
- private final Session2Link mSessionLink;
private final ComponentName mComponentName;
+ private final Bundle mExtras;
+
+ private boolean mDestroyed = false;
/**
* Constructor for the token with type {@link #TYPE_SESSION_SERVICE}.
@@ -106,44 +121,67 @@ public final class Session2Token implements Parcelable {
final PackageManager manager = context.getPackageManager();
final int uid = getUid(manager, serviceComponent.getPackageName());
- if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE,
- serviceComponent)) {
+ if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) {
Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service.");
}
+ mSessionId = null;
mComponentName = serviceComponent;
mPackageName = serviceComponent.getPackageName();
mServiceName = serviceComponent.getClassName();
+ mPid = -1;
mUid = uid;
mType = TYPE_SESSION_SERVICE;
- mSessionLink = null;
+ mExtras = null;
}
- Session2Token(int uid, int type, String packageName, Session2Link sessionLink) {
- mUid = uid;
- mType = type;
- mPackageName = packageName;
+ /**
+ * Constructor for the token with type {@link #TYPE_SESSION}.
+ *
+ * @param context The context.
+ * @param sessionId The ID of the session. Should be unique.
+ * @param extras The extras.
+ * @hide
+ */
+ @SystemApi
+ public Session2Token(@NonNull Context context, @NonNull String sessionId,
+ @Nullable Bundle extras) {
+ if (sessionId == null) {
+ throw new IllegalArgumentException("sessionId shouldn't be null");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("context shouldn't be null");
+ }
+ mSessionId = sessionId;
+ mPid = Process.myPid();
+ mUid = Process.myUid();
+ mType = TYPE_SESSION;
+ mPackageName = context.getPackageName();
+ mExtras = extras;
mServiceName = null;
mComponentName = null;
- mSessionLink = sessionLink;
}
Session2Token(Parcel in) {
+ mSessionId = in.readString();
+ mPid = in.readInt();
mUid = in.readInt();
mType = in.readInt();
mPackageName = in.readString();
mServiceName = in.readString();
- mSessionLink = in.readParcelable(null);
mComponentName = ComponentName.unflattenFromString(in.readString());
+ mExtras = in.readParcelable(null);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mSessionId);
+ dest.writeInt(mPid);
dest.writeInt(mUid);
dest.writeInt(mType);
dest.writeString(mPackageName);
dest.writeString(mServiceName);
- dest.writeParcelable(mSessionLink, flags);
dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
+ dest.writeParcelable(mExtras, flags);
}
@Override
@@ -153,7 +191,7 @@ public final class Session2Token implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink);
+ return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName);
}
@Override
@@ -162,17 +200,27 @@ public final class Session2Token implements Parcelable {
return false;
}
Session2Token other = (Session2Token) obj;
- return mUid == other.mUid
- && TextUtils.equals(mPackageName, other.mPackageName)
- && TextUtils.equals(mServiceName, other.mServiceName)
+ return TextUtils.equals(mSessionId, other.mSessionId)
+ && mPid == other.mPid
+ && mUid == other.mUid
&& mType == other.mType
- && Objects.equals(mSessionLink, other.mSessionLink);
+ && TextUtils.equals(mPackageName, other.mPackageName)
+ && TextUtils.equals(mServiceName, other.mServiceName);
}
@Override
public String toString() {
return "Session2Token {pkg=" + mPackageName + " type=" + mType
- + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}";
+ + " service=" + mServiceName + "}";
+ }
+
+ /**
+ * @return pid of the session
+ * @hide
+ */
+ @SystemApi
+ public int getPid() {
+ return mPid;
}
/**
@@ -207,8 +255,36 @@ public final class Session2Token implements Parcelable {
return mType;
}
- Session2Link getSessionLink() {
- return mSessionLink;
+ /**
+ * @return extras
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras == null ? new Bundle() : new Bundle(mExtras);
+ }
+
+ /**
+ * Destroys this session token. After this method is called,
+ * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called
+ * with this token.
+ *
+ * @see MediaSessionManager#notifySession2Created(Session2Token)
+ * @hide
+ */
+ @SystemApi
+ public void destroy() {
+ mDestroyed = true;
+ }
+
+ /**
+ * @return whether this token is destroyed
+ * @hide
+ */
+ @SystemApi
+ public boolean isDestroyed() {
+ return mDestroyed;
}
private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index ed162504c553..fa6e03430315 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -37,6 +37,7 @@ interface ISessionManager {
SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
int userId);
void notifySession2Created(in Session2Token sessionToken);
+ void notifySession2Destroyed(in Session2Token sessionToken);
List<ControllerLink> getSessions(in ComponentName compName, int userId);
List<Session2Token> getSession2Tokens(int userId);
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c64c452be3ef..cae4d1749287 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -26,7 +26,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.media.AudioManager;
import android.media.IRemoteVolumeController;
-import android.media.MediaSession2;
import android.media.Session2Token;
import android.os.Handler;
import android.os.IBinder;
@@ -115,11 +114,11 @@ public final class MediaSessionManager {
}
/**
- * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
+ * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is
* created.
* <p>
* Do not use this API directly, but create a new instance through the
- * {@link MediaSession2.Builder} instead.
+ * MediaSession2.Builder instead.
*
* @param token newly created session2 token
*/
@@ -130,6 +129,9 @@ public final class MediaSessionManager {
if (token.getType() != Session2Token.TYPE_SESSION) {
throw new IllegalArgumentException("token's type should be TYPE_SESSION");
}
+ if (token.isDestroyed()) {
+ throw new IllegalArgumentException("token is already destroyed");
+ }
try {
mService.notifySession2Created(token);
} catch (RemoteException e) {
@@ -138,6 +140,31 @@ public final class MediaSessionManager {
}
/**
+ * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is
+ * destroyed.
+ * <p>
+ * Do not use this API directly, but close a session with MediaSession2#close() instead.
+ *
+ * @param token destroyed session2 token
+ */
+ public void notifySession2Destroyed(@NonNull Session2Token token) {
+ if (token == null) {
+ throw new IllegalArgumentException("token shouldn't be null");
+ }
+ if (token.getType() != Session2Token.TYPE_SESSION) {
+ throw new IllegalArgumentException("token's type should be TYPE_SESSION");
+ }
+ if (!token.isDestroyed()) {
+ throw new IllegalArgumentException("token should have been destroyed");
+ }
+ try {
+ mService.notifySession2Destroyed(token);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get a list of controllers for all ongoing sessions. The controllers will
* be provided in priority order with the most important controller at index
* 0.
@@ -192,7 +219,7 @@ public final class MediaSessionManager {
* current user.
* <p>
* Although this API can be used without any restriction, each session owners can accept or
- * reject your uses of {@link MediaSession2}.
+ * reject your uses of MediaSession2.
*
* @return A list of {@link Session2Token}.
*/
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 48dbf555e546..852d2962ee66 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -101,7 +101,7 @@ cc_library_shared {
"libhidlbase",
"libhidlmemory",
- "libmediametrics", // Used by MediaMetrics. Will be replaced with stable C API.
+ "libmediametrics",
"libbinder", // Used by JWakeLock and MediaMetrics.
"libutils", // Have to use shared lib to make libandroid_runtime behave correctly.
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a45aa90f5f19..417a427b6c7c 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -405,6 +405,11 @@ static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint w
nativeFormat, consumerUsage);
return;
}
+
+ if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+ gbConsumer->setConsumerIsProtected(true);
+ }
+
ctx->setBufferConsumer(bufferConsumer);
bufferConsumer->setName(consumerName);
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 3ded8c260512..de60b085b87d 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "MediaMetricsJNI"
+
#include <jni.h>
#include <nativehelper/JNIHelp.h>
@@ -21,7 +23,10 @@
#include <media/MediaAnalyticsItem.h>
-// Copeid from core/jni/ (libandroid_runtime.so)
+// This source file is compiled and linked into both:
+// core/jni/ (libandroid_runtime.so)
+// media/jni (libmedia2_jni.so)
+
namespace android {
// place the attributes into a java PersistableBundle object
@@ -29,7 +34,7 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i
jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
if (clazzBundle==NULL) {
- ALOGD("can't find android/os/PersistableBundle");
+ ALOGE("can't find android/os/PersistableBundle");
return NULL;
}
// sometimes the caller provides one for us to fill
@@ -86,5 +91,138 @@ jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *i
return mybundle;
}
+// convert the specified batch metrics attributes to a persistent bundle.
+// The encoding of the byte array is specified in
+// frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp
+//
+// type encodings; matches frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp
+enum { kInt32 = 0, kInt64, kDouble, kRate, kCString};
+
+jobject MediaMetricsJNI::writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length) {
+ ALOGV("writeAttributes()");
+
+ if (buffer == NULL || length <= 0) {
+ ALOGW("bad parameters to writeAttributesToBundle()");
+ return NULL;
+ }
+
+ jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
+ if (clazzBundle==NULL) {
+ ALOGE("can't find android/os/PersistableBundle");
+ return NULL;
+ }
+ // sometimes the caller provides one for us to fill
+ if (mybundle == NULL) {
+ // create the bundle
+ jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
+ mybundle = env->NewObject(clazzBundle, constructID);
+ if (mybundle == NULL) {
+ ALOGD("unable to create mybundle");
+ return NULL;
+ }
+ }
+
+ int left = length;
+ char *buf = buffer;
+
+ // grab methods that we can invoke
+ jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
+ jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
+ jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
+ jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
+
+
+#define _EXTRACT(size, val) \
+ { if ((size) > left) goto badness; memcpy(&val, buf, (size)); buf += (size); left -= (size);}
+#define _SKIP(size) \
+ { if ((size) > left) goto badness; buf += (size); left -= (size);}
+
+ int32_t bufsize;
+ _EXTRACT(sizeof(int32_t), bufsize);
+ if (bufsize != length) {
+ goto badness;
+ }
+ int32_t proto;
+ _EXTRACT(sizeof(int32_t), proto);
+ if (proto != 0) {
+ ALOGE("unsupported wire protocol %d", proto);
+ goto badness;
+ }
+
+ int32_t count;
+ _EXTRACT(sizeof(int32_t), count);
+
+ // iterate through my attributes
+ // -- get name, get type, get value, insert into bundle appropriately.
+ for (int i = 0 ; i < count; i++ ) {
+ // prop name len (int16)
+ int16_t keylen;
+ _EXTRACT(sizeof(int16_t), keylen);
+ if (keylen <= 0) goto badness;
+ // prop name itself
+ char *key = buf;
+ jstring keyName = env->NewStringUTF(buf);
+ _SKIP(keylen);
+
+ // prop type (int8_t)
+ int8_t attrType;
+ _EXTRACT(sizeof(int8_t), attrType);
+
+ int16_t attrSize;
+ _EXTRACT(sizeof(int16_t), attrSize);
+
+ switch (attrType) {
+ case kInt32:
+ {
+ int32_t i32;
+ _EXTRACT(sizeof(int32_t), i32);
+ env->CallVoidMethod(mybundle, setIntID,
+ keyName, (jint) i32);
+ break;
+ }
+ case kInt64:
+ {
+ int64_t i64;
+ _EXTRACT(sizeof(int64_t), i64);
+ env->CallVoidMethod(mybundle, setLongID,
+ keyName, (jlong) i64);
+ break;
+ }
+ case kDouble:
+ {
+ double d64;
+ _EXTRACT(sizeof(double), d64);
+ env->CallVoidMethod(mybundle, setDoubleID,
+ keyName, (jdouble) d64);
+ break;
+ }
+ case kCString:
+ {
+ jstring value = env->NewStringUTF(buf);
+ env->CallVoidMethod(mybundle, setStringID,
+ keyName, value);
+ _SKIP(attrSize);
+ break;
+ }
+ default:
+ ALOGW("ignoring Attribute '%s' unknown type: %d",
+ key, attrType);
+ _SKIP(attrSize);
+ break;
+ }
+ }
+
+ // should have consumed it all
+ if (left != 0) {
+ ALOGW("did not consume entire buffer; left(%d) != 0", left);
+ goto badness;
+ }
+
+ return mybundle;
+
+ badness:
+ return NULL;
+}
+
}; // namespace android
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index fd621ea7261d..a10780f5c5c3 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -27,6 +27,7 @@ namespace android {
class MediaMetricsJNI {
public:
static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+ static jobject writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length);
};
}; // namespace android
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 9b4e730cfb5e..306916121740 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -786,22 +786,17 @@ android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz)
return 0;
}
- Parcel p;
- int key = FOURCC('m','t','r','X');
- status_t status = mp->getParameter(key, &p);
+ char *buffer = NULL;
+ size_t length = 0;
+ status_t status = mp->getMetrics(&buffer, &length);
if (status != OK) {
ALOGD("getMetrics() failed: %d", status);
return (jobject) NULL;
}
- p.setDataPosition(0);
- MediaAnalyticsItem *item = new MediaAnalyticsItem;
- item->readFromParcel(p);
- jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+ jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length);
- // housekeeping
- delete item;
- item = NULL;
+ free(buffer);
return mybundle;
}
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 5fae9d5a7974..3156732ef02b 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -47,32 +47,13 @@ using Transaction = SurfaceComposerClient::Transaction;
static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
sp<SurfaceComposerClient> client = surfaceControl->getClient();
sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
-
- Vector<ui::ColorMode> colorModes;
- status_t err = client->getDisplayColorModes(display, &colorModes);
+ bool isWideColorDisplay = false;
+ status_t err = client->isWideColorDisplay(display, &isWideColorDisplay);
if (err) {
ALOGE("unable to get wide color support");
return false;
}
-
- bool wideColorBoardConfig =
- getBool<ISurfaceFlingerConfigs,
- &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
-
- for (android::ui::ColorMode colorMode : colorModes) {
- switch (colorMode) {
- case ui::ColorMode::DISPLAY_P3:
- case ui::ColorMode::ADOBE_RGB:
- case ui::ColorMode::DCI_P3:
- if (wideColorBoardConfig) {
- return true;
- }
- break;
- default:
- break;
- }
- }
- return false;
+ return isWideColorDisplay;
}
static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
diff --git a/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
index 0490e650a7a4..e31ce195214f 100644
--- a/native/webview/plat_support/draw_fn.h
+++ b/native/webview/plat_support/draw_fn.h
@@ -20,7 +20,8 @@ extern "C" {
// android to chromium are versioned.
//
// 1 is Android Q. This matches kAwDrawGLInfoVersion version 3.
-static const int kAwDrawFnVersion = 1;
+// 2 Adds transfer_function_* and color_space_toXYZD50 to AwDrawFn_DrawGLParams.
+static const int kAwDrawFnVersion = 2;
struct AwDrawFn_OnSyncParams {
int version;
@@ -64,6 +65,16 @@ struct AwDrawFn_DrawGLParams {
// Input: current transformation matrix in surface pixels.
// Uses the column-based OpenGL matrix format.
float transform[16];
+
+ // Input: Color space parameters.
+ float transfer_function_g;
+ float transfer_function_a;
+ float transfer_function_b;
+ float transfer_function_c;
+ float transfer_function_d;
+ float transfer_function_e;
+ float transfer_function_f;
+ float color_space_toXYZD50[9];
};
struct AwDrawFn_InitVkParams {
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index b97bbc311624..afe103a25043 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -55,6 +55,8 @@ void onDestroyed(int functor, void* data) {
void draw_gl(int functor, void* data,
const uirenderer::DrawGlInfo& draw_gl_params) {
+ float gabcdef[7];
+ draw_gl_params.color_space_ptr->transferFn(gabcdef);
AwDrawFn_DrawGLParams params = {
.version = kAwDrawFnVersion,
.clip_left = draw_gl_params.clipLeft,
@@ -64,12 +66,24 @@ void draw_gl(int functor, void* data,
.width = draw_gl_params.width,
.height = draw_gl_params.height,
.is_layer = draw_gl_params.isLayer,
+ .transfer_function_g = gabcdef[0],
+ .transfer_function_a = gabcdef[1],
+ .transfer_function_b = gabcdef[2],
+ .transfer_function_c = gabcdef[3],
+ .transfer_function_d = gabcdef[4],
+ .transfer_function_e = gabcdef[5],
+ .transfer_function_f = gabcdef[6],
};
COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_gl_params.transform),
mismatched_transform_matrix_sizes);
for (int i = 0; i < NELEM(params.transform); ++i) {
params.transform[i] = draw_gl_params.transform[i];
}
+ COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3),
+ gamut_transform_size_mismatch);
+ draw_gl_params.color_space_ptr->toXYZD50(
+ reinterpret_cast<skcms_Matrix3x3*>(&params.color_space_toXYZD50));
+
SupportData* support = static_cast<SupportData*>(data);
support->callbacks.draw_gl(functor, support->data, &params);
}
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 83084c519f19..7eaf04bfb775 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -17,7 +17,6 @@
package com.android.captiveportallogin;
import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
-import static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME;
import android.app.Activity;
import android.app.AlertDialog;
@@ -40,8 +39,6 @@ import android.net.http.SslError;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Bundle;
-import android.provider.Settings;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -61,16 +58,17 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
-import java.lang.InterruptedException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -81,6 +79,7 @@ public class CaptivePortalLoginActivity extends Activity {
private static final boolean VDBG = false;
private static final int SOCKET_TIMEOUT_MS = 10000;
+ public static final String HTTP_LOCATION_HEADER_NAME = "Location";
private enum Result {
DISMISSED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED),
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 9b6ad38545b4..9064ebe80da1 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -81,5 +81,5 @@ android_app {
"com.android.keyguard",
],
- annotation_processors: ["dagger2-compiler-2.19"],
+ plugins: ["dagger2-compiler-2.19"],
}
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 052566d67c1b..e591ea90c112 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -65,8 +65,9 @@
<com.android.systemui.statusbar.car.CarFacetButton
android:id="@+id/music_nav"
style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MUSIC"
systemui:icon="@drawable/car_ic_music"
- systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
systemui:packages="com.android.car.media"
systemui:selectedIcon="@drawable/car_ic_music_selected"
systemui:useMoreIcon="false"
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index dbddf71d342c..b37c5e69df76 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -114,9 +114,16 @@ public class CarStatusBar extends StatusBar implements
new DeviceProvisionedController.DeviceProvisionedListener() {
@Override
public void onDeviceProvisionedChanged() {
- mDeviceIsProvisioned =
- mDeviceProvisionedController.isDeviceProvisioned();
- restartNavBars();
+ mHandler.post(() -> {
+ // on initial boot we are getting a call even though the value
+ // is the same so we are confirming the reset is needed
+ boolean deviceProvisioned =
+ mDeviceProvisionedController.isDeviceProvisioned();
+ if (mDeviceIsProvisioned != deviceProvisioned) {
+ mDeviceIsProvisioned = deviceProvisioned;
+ restartNavBars();
+ }
+ });
}
});
}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index 0d528e7078f8..5acf4fbaa5cb 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -202,7 +202,7 @@ public class SmartActionsHelper {
}
TextClassifierEvent textClassifierEvent =
createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId)
- .setEntityType(ConversationAction.TYPE_TEXT_REPLY)
+ .setEntityTypes(ConversationAction.TYPE_TEXT_REPLY)
.build();
mTextClassifier.onTextClassifierEvent(textClassifierEvent);
}
@@ -225,7 +225,7 @@ public class SmartActionsHelper {
}
TextClassifierEvent textClassifierEvent =
createTextClassifierEventBuilder(TextClassifierEvent.TYPE_SMART_ACTION, resultId)
- .setEntityType(actionType)
+ .setEntityTypes(actionType)
.build();
mTextClassifier.onTextClassifierEvent(textClassifierEvent);
}
@@ -291,7 +291,7 @@ public class SmartActionsHelper {
Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
if (messages == null || messages.length == 0) {
return Arrays.asList(new ConversationActions.Message.Builder(
- ConversationActions.Message.PERSON_USER_REMOTE)
+ ConversationActions.Message.PERSON_USER_OTHERS)
.setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
.build());
}
@@ -310,7 +310,7 @@ public class SmartActionsHelper {
break;
}
Person author = localUser != null && localUser.equals(senderPerson)
- ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson;
+ ? ConversationActions.Message.PERSON_USER_SELF : senderPerson;
extractMessages.push(new ConversationActions.Message.Builder(author)
.setText(message.getText())
.setReferenceTime(
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index 707349b0fd15..7f8127aa43a8 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -154,7 +154,7 @@ public class SmartActionHelperTest {
ConversationActions.Message secondMessage = messages.get(0);
MessageSubject.assertThat(secondMessage).hasText("secondMessage");
MessageSubject.assertThat(secondMessage)
- .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL);
+ .hasPerson(ConversationActions.Message.PERSON_USER_SELF);
MessageSubject.assertThat(secondMessage)
.hasReferenceTime(createZonedDateTimeFromMsUtc(2000));
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index ad7f85d0a30a..f20e01636d72 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -29,7 +29,6 @@ import android.net.INetd;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.Network;
import android.net.ProvisioningConfigurationParcelable;
import android.net.ProxyInfo;
import android.net.ProxyInfoParcelable;
@@ -1000,7 +999,9 @@ public class IpClient extends StateMachine {
// mDhcpResults is never shared with any other owner so we don't have
// to worry about concurrent modification.
if (mDhcpResults != null) {
- for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+ final List<RouteInfo> routes =
+ mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName);
+ for (RouteInfo route : routes) {
newLp.addRoute(route);
}
addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 6b31b82ec3cc..0cca778a9612 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -93,6 +93,7 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -1146,7 +1147,10 @@ public class NetworkMonitor extends StateMachine {
return null;
}
- return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+ final Collection<CaptivePortalProbeSpec> specs =
+ CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+ final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()];
+ return specs.toArray(specsArray);
} catch (Exception e) {
// Don't let a misconfiguration bootloop the system.
Log.e(TAG, "Error parsing configured fallback probe specs", e);
diff --git a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index f21809fdbc1c..4ae044dec20b 100644
--- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -103,9 +103,7 @@ public class IpClientTest {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
- when(mContext.getSystemServiceName(ConnectivityManager.class))
- .thenReturn(Context.CONNECTIVITY_SERVICE);
- when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
+ when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
.thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 74aaf3c26aba..93f6a94dcf49 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -47,6 +47,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import java.util.List;
+import java.util.Set;
/**
* Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -325,7 +326,8 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
if (admin == null) {
return null;
}
- if (dpm.getCrossProfileCalendarPackages().isEmpty()) {
+ final Set<String> packages = dpm.getCrossProfileCalendarPackages();
+ if (packages != null && packages.isEmpty()) {
return admin;
}
return null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 7357fe63d9b0..42afb69a27f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
@@ -123,4 +124,12 @@ public class AppUtils {
return null;
}
+ /**
+ * Returns a boolean indicating whether the given package is a hidden system module
+ */
+ public static boolean isHiddenSystemModule(Context context, String packageName) {
+ return ApplicationsState.getInstance((Application) context.getApplicationContext())
+ .isHiddenModule(packageName);
+ }
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index a936df2bf2eb..c9fbc7ba9f05 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -29,6 +29,7 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageStats;
@@ -71,6 +72,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -95,9 +97,14 @@ public class ApplicationsState {
static ApplicationsState sInstance;
public static ApplicationsState getInstance(Application app) {
+ return getInstance(app, AppGlobals.getPackageManager());
+ }
+
+ @VisibleForTesting
+ static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) {
synchronized (sLock) {
if (sInstance == null) {
- sInstance = new ApplicationsState(app);
+ sInstance = new ApplicationsState(app, iPackageManager);
}
return sInstance;
}
@@ -132,6 +139,7 @@ public class ApplicationsState {
String mCurComputingSizePkg;
int mCurComputingSizeUserId;
boolean mSessionsChanged;
+ final HashSet<String> mHiddenModules = new HashSet<>();
// Temporary for dispatching session callbacks. Only touched by main thread.
final ArrayList<WeakReference<Session>> mActiveSessions = new ArrayList<>();
@@ -172,11 +180,11 @@ public class ApplicationsState {
FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS |
FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
- private ApplicationsState(Application app) {
+ private ApplicationsState(Application app, IPackageManager iPackageManager) {
mContext = app;
mPm = mContext.getPackageManager();
mDrawableFactory = IconDrawableFactory.newInstance(mContext);
- mIpm = AppGlobals.getPackageManager();
+ mIpm = iPackageManager;
mUm = mContext.getSystemService(UserManager.class);
mStats = mContext.getSystemService(StorageStatsManager.class);
for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
@@ -194,6 +202,13 @@ public class ApplicationsState {
mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
+ for (ModuleInfo info : moduleInfos) {
+ if (info.isHidden()) {
+ mHiddenModules.add(info.getPackageName());
+ }
+ }
+
/**
* This is a trick to prevent the foreground thread from being delayed.
* The problem is that Dalvik monitors are initially spin locks, to keep
@@ -283,6 +298,10 @@ public class ApplicationsState {
}
mHaveDisabledApps = true;
}
+ if (isHiddenModule(info.packageName)) {
+ mApplications.remove(i--);
+ continue;
+ }
if (!mHaveInstantApps && AppUtils.isInstant(info)) {
mHaveInstantApps = true;
}
@@ -314,10 +333,15 @@ public class ApplicationsState {
public boolean haveDisabledApps() {
return mHaveDisabledApps;
}
+
public boolean haveInstantApps() {
return mHaveInstantApps;
}
+ boolean isHiddenModule(String packageName) {
+ return mHiddenModules.contains(packageName);
+ }
+
void doPauseIfNeededLocked() {
if (!mResumed) {
return;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index d9578014e846..305a1ff97e71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -22,7 +22,6 @@ import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
-import android.net.ConnectivityManager;
import android.net.INetworkStatsService;
import android.net.INetworkStatsSession;
import android.net.NetworkPolicy;
@@ -35,14 +34,14 @@ import android.os.ServiceManager;
import android.text.format.DateUtils;
import android.util.Pair;
+import androidx.annotation.VisibleForTesting;
+import androidx.loader.content.AsyncTaskLoader;
+
import com.android.settingslib.NetworkPolicyEditor;
import java.time.ZonedDateTime;
import java.util.Iterator;
-import androidx.annotation.VisibleForTesting;
-import androidx.loader.content.AsyncTaskLoader;
-
/**
* Loader for network data usage history. It returns a list of usage data per billing cycle.
*/
@@ -121,8 +120,7 @@ public abstract class NetworkCycleDataLoader<D> extends AsyncTaskLoader<D> {
long cycleEnd = historyEnd;
while (cycleEnd > historyStart) {
- final long cycleStart = Math.max(
- historyStart, cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4));
+ final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
recordUsage(cycleStart, cycleEnd);
cycleEnd = cycleStart;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index ccec175aefad..a098ecc17b3d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -18,7 +18,9 @@ package com.android.settingslib.applications;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
@@ -33,12 +35,16 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.IconDrawableFactory;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -46,11 +52,11 @@ import com.android.settingslib.applications.ApplicationsState.Callbacks;
import com.android.settingslib.applications.ApplicationsState.Session;
import com.android.settingslib.testutils.shadow.ShadowUserManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -78,6 +84,8 @@ public class ApplicationsStateRoboTest {
/** Class under test */
private ApplicationsState mApplicationsState;
+ private Session mSession;
+
@Mock
private Callbacks mCallbacks;
@@ -85,6 +93,8 @@ public class ApplicationsStateRoboTest {
private ArgumentCaptor<ArrayList<AppEntry>> mAppEntriesCaptor;
@Mock
private StorageStatsManager mStorageStatsManager;
+ @Mock
+ private IPackageManager mPackageManagerService;
@Implements(value = IconDrawableFactory.class)
public static class ShadowIconDrawableFactory {
@@ -99,6 +109,11 @@ public class ApplicationsStateRoboTest {
public static class ShadowPackageManager extends
org.robolectric.shadows.ShadowApplicationPackageManager {
+ // test installed modules, 2 regular, 2 hidden
+ private final String[] mModuleNames = {
+ "test.module.1", "test.hidden.module.2", "test.hidden.module.3", "test.module.4"};
+ private final List<ModuleInfo> mInstalledModules = new ArrayList<>();
+
@Implementation
protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
ResolveInfo resolveInfo = new ResolveInfo();
@@ -109,6 +124,16 @@ public class ApplicationsStateRoboTest {
return ComponentName.createRelative(resolveInfo.activityInfo.packageName, "foo");
}
+ @Implementation
+ public List<ModuleInfo> getInstalledModules(int flags) {
+ if (mInstalledModules.isEmpty()) {
+ for (String moduleName : mModuleNames) {
+ mInstalledModules.add(createModuleInfo(moduleName));
+ }
+ }
+ return mInstalledModules;
+ }
+
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
@PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) {
List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -121,6 +146,15 @@ public class ApplicationsStateRoboTest {
resolveInfos.add(resolveInfo);
return resolveInfos;
}
+
+ private ModuleInfo createModuleInfo(String packageName) {
+ final ModuleInfo info = new ModuleInfo();
+ info.setName(packageName);
+ info.setPackageName(packageName);
+ // will treat any app with package name that contains "hidden" as hidden module
+ info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
+ return info;
+ }
}
@Before
@@ -136,12 +170,28 @@ public class ApplicationsStateRoboTest {
storageStats.codeBytes = 10;
storageStats.dataBytes = 20;
storageStats.cacheBytes = 30;
- when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class),
- anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats);
+ when(mStorageStatsManager.queryStatsForPackage(any(UUID.class),
+ anyString(), any(UserHandle.class))).thenReturn(storageStats);
+
+ // Set up 3 installed apps, in which 1 is hidden module
+ final List<ApplicationInfo> infos = new ArrayList<>();
+ infos.add(createApplicationInfo("test.package.1"));
+ infos.add(createApplicationInfo("test.hidden.module.2"));
+ infos.add(createApplicationInfo("test.package.3"));
+ when(mPackageManagerService.getInstalledApplications(
+ anyInt() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos));
ApplicationsState.sInstance = null;
- mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application);
+ mApplicationsState =
+ ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService);
mApplicationsState.clearEntries();
+
+ mSession = mApplicationsState.newSession(mCallbacks);
+ }
+
+ @After
+ public void tearDown() {
+ mSession.onDestroy();
}
private ApplicationInfo createApplicationInfo(String packageName) {
@@ -187,12 +237,11 @@ public class ApplicationsStateRoboTest {
@Test
public void testDefaultSessionLoadsAll() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.onResume();
+ mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
addApp(LAUNCHABLE_PACKAGE_NAME, 2);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -211,17 +260,15 @@ public class ApplicationsStateRoboTest {
AppEntry launchableEntry = findAppEntry(appEntries, 2);
assertThat(launchableEntry.hasLauncherEntry).isTrue();
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsIconsOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS);
+ mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -232,17 +279,15 @@ public class ApplicationsStateRoboTest {
assertThat(launchableEntry.icon).isNotNull();
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.hasLauncherEntry).isFalse();
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsSizesOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES);
+ mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -253,17 +298,15 @@ public class ApplicationsStateRoboTest {
assertThat(launchableEntry.icon).isNull();
assertThat(launchableEntry.hasLauncherEntry).isFalse();
assertThat(launchableEntry.size).isGreaterThan(0L);
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsHomeOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP);
+ mSession.onResume();
addApp(HOME_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -275,17 +318,15 @@ public class ApplicationsStateRoboTest {
assertThat(launchableEntry.hasLauncherEntry).isFalse();
assertThat(launchableEntry.size).isEqualTo(-1);
assertThat(launchableEntry.isHomeApp).isTrue();
- session.onDestroy();
}
@Test
public void testCustomSessionLoadsLeanbackOnly() {
- Session session = mApplicationsState.newSession(mCallbacks);
- session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
- session.onResume();
+ mSession.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER);
+ mSession.onResume();
addApp(LAUNCHABLE_PACKAGE_NAME, 1);
- session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
+ mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR);
processAllMessages();
verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture());
@@ -298,6 +339,16 @@ public class ApplicationsStateRoboTest {
assertThat(launchableEntry.isHomeApp).isFalse();
assertThat(launchableEntry.hasLauncherEntry).isTrue();
assertThat(launchableEntry.launcherEntryEnabled).isTrue();
- session.onDestroy();
}
+
+ @Test
+ public void onResume_shouldNotIncludeSystemHiddenModule() {
+ mSession.onResume();
+
+ final List<ApplicationInfo> mApplications = mApplicationsState.mApplications;
+ assertThat(mApplications).hasSize(2);
+ assertThat(mApplications.get(0).packageName).isEqualTo("test.package.1");
+ assertThat(mApplications.get(1).packageName).isEqualTo("test.package.3");
+ }
+
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index 2d8ea125a97e..b8a143a376fd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -130,7 +130,8 @@ public class NetworkCycleDataLoaderTest {
.thenReturn(networkHistory);
final long now = System.currentTimeMillis();
final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4);
- when(networkHistory.getStart()).thenReturn(fourWeeksAgo);
+ final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2);
+ when(networkHistory.getStart()).thenReturn(twoDaysAgo);
when(networkHistory.getEnd()).thenReturn(now);
mLoader.loadFourWeeksData();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 64d68bf6f998..4f6a4ad94479 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -698,6 +698,9 @@ class SettingsProtoDumpUtil {
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
dumpSetting(s, p,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
+ GlobalSettingsProto.Gpu.ANGLE_WHITELIST);
+ dumpSetting(s, p,
Settings.Global.GPU_DEBUG_LAYER_APP,
GlobalSettingsProto.Gpu.DEBUG_LAYER_APP);
dumpSetting(s, p,
@@ -819,6 +822,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
GlobalSettingsProto.Location.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS);
+ dumpSetting(s, p,
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
+ GlobalSettingsProto.Location.IGNORE_SETTINGS_PACKAGE_WHITELIST);
p.end(locationToken);
final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE);
@@ -2389,6 +2395,10 @@ class SettingsProtoDumpUtil {
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+ dumpSetting(s, p,
+ Settings.Secure.AWARE_ENABLED,
+ SecureSettingsProto.AWARE_ENABLED);
+
// Please insert new settings using the same order as in SecureSettingsProto.
p.end(token);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8be67d9a7a51..e0d178fb9a1e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -57,6 +57,7 @@ android_library {
"androidx.slice_slice-builders",
"androidx.arch.core_core-runtime",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.dynamicanimation_dynamicanimation",
"SystemUI-tags",
"SystemUI-proto",
"dagger2-2.19",
@@ -73,7 +74,7 @@ android_library {
"com.android.keyguard",
],
- annotation_processors: ["dagger2-compiler-2.19"],
+ plugins: ["dagger2-compiler-2.19"],
}
android_library {
@@ -108,6 +109,7 @@ android_library {
"androidx.slice_slice-builders",
"androidx.arch.core_core-runtime",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.dynamicanimation_dynamicanimation",
"SystemUI-tags",
"SystemUI-proto",
"metrics-helper-lib",
@@ -127,7 +129,7 @@ android_library {
"--extra-packages",
"com.android.keyguard:com.android.systemui",
],
- annotation_processors: ["dagger2-compiler-2.19"],
+ plugins: ["dagger2-compiler-2.19"],
}
android_app {
diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
new file mode 100644
index 000000000000..c3a45e294e79
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
new file mode 100644
index 000000000000..e77c676bc13f
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
new file mode 100644
index 000000000000..a67b5e873b2e
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -0,0 +1,56 @@
+# Physics Animation Layout
+
+## Overview
+**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
+
+An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.
+
+## PhysicsAnimationController
+PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events.
+
+### Configuration Methods
+![Diagram of how animations are configured using the controller's configuration methods.](physics-animation-layout-config-methods.png)
+The controller must override the following methods:
+
+```Set<ViewProperty> getAnimatedProperties()```
+Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations.
+
+```int getNextAnimationInChain(ViewProperty property, int index)```
+If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind.
+
+```float getOffsetForChainedPropertyAnimation(ViewProperty property)```
+Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc.
+
+```SpringForce getSpringForce(ViewProperty property)```
+Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed.
+
+### Animation Control Methods
+![Diagram of how calls to animateValueForChildAtIndex dispatch to DynamicAnimations.](physics-animation-layout-control-methods.png)
+Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.
+
+In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation.
+
+For example, moving the first child view to *(100, 200)*:
+
+```
+animateValueForChildAtIndex(TRANSLATION_X, 0, 100);
+animateValueForChildAtIndex(TRANSLATION_Y, 0, 200);
+```
+
+This would use the physics animations constructed by the layout to spring the view to *(100, 200)*.
+
+If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```.
+
+## PhysicsAnimationLayout
+The layout itself is a FrameLayout descendant with a few extra methods:
+
+```setController(PhysicsAnimationController controller)```
+Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.
+
+```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
+Sets an end listener that is called when all animations on the given property have ended.
+
+```setMaxRenderedChildren(int max)```
+Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
+
+It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation. \ No newline at end of file
diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md
new file mode 100644
index 000000000000..47354d45fa33
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-testing.md
@@ -0,0 +1,11 @@
+# Physics Animation Testing
+Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic.
+
+For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions.
+
+## Waiting for Animations to End
+Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations.
+
+To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important.
+
+The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete. \ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
index 0b1dab1c3bca..fc84332151ec 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java
@@ -20,14 +20,14 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import java.util.ArrayList;
-
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.annotations.DependsOn;
import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
+
+import java.util.ArrayList;
@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION,
version = NotificationMenuRowPlugin.VERSION)
@@ -149,6 +149,12 @@ public interface NotificationMenuRowPlugin extends Plugin {
public boolean canBeDismissed();
/**
+ * Informs the menu whether dismiss gestures are left-to-right or right-to-left.
+ */
+ default void setDismissRtl(boolean dismissRtl) {
+ }
+
+ /**
* Determines whether the menu should remain open given its current state, or snap closed.
* @return true if the menu should remain open, false otherwise.
*/
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index 204408cda81f..13186fc6437c 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -22,8 +22,8 @@
<com.android.systemui.bubbles.BadgedImageView
android:id="@+id/bubble_image"
- android:layout_width="@dimen/bubble_size"
- android:layout_height="@dimen/bubble_size"
+ android:layout_width="@dimen/individual_bubble_size"
+ android:layout_height="@dimen/individual_bubble_size"
android:padding="@dimen/bubble_view_padding"
android:clipToPadding="false"/>
diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml
new file mode 100644
index 000000000000..e6f2376ae76b
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.globalactions.GlobalActionsGridLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@id/global_actions_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:theme="@style/qs_theme"
+ android:gravity="bottom|center"
+ android:clipChildren="false"
+>
+
+ <LinearLayout
+ android:layout_height="290dp"
+ android:layout_width="412dp"
+ android:gravity="bottom"
+ android:padding="0dp"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin"
+ >
+ <!-- For separated items-->
+ <LinearLayout
+ android:id="@+id/separated_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:paddingTop="@dimen/global_actions_grid_top_padding"
+ android:paddingLeft="@dimen/global_actions_grid_left_padding"
+ android:paddingBottom="@dimen/global_actions_grid_bottom_padding"
+ android:paddingRight="@dimen/global_actions_grid_right_padding"
+ android:orientation="vertical"
+ android:background="?android:attr/colorBackgroundFloating"
+ android:translationZ="@dimen/global_actions_translate"
+ />
+
+ <Space android:layout_width="match_parent" android:layout_height="2dp"
+ android:layout_weight="1" />
+
+ <!-- Grid of action items -->
+ <com.android.systemui.globalactions.ListGridLayout
+ android:id="@android:id/list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:orientation="horizontal"
+ android:layoutDirection="rtl"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:translationZ="@dimen/global_actions_translate"
+ android:paddingLeft="@dimen/global_actions_grid_left_padding"
+ android:paddingRight="@dimen/global_actions_grid_right_padding"
+ android:paddingTop="@dimen/global_actions_grid_top_padding"
+ android:paddingBottom="@dimen/global_actions_grid_bottom_padding"
+ android:background="?android:attr/colorBackgroundFloating"
+ >
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:visibility="gone"
+ android:gravity="bottom"
+ android:orientation="vertical"
+ />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:visibility="gone"
+ android:gravity="bottom"
+ android:orientation="vertical"
+ />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|right"
+ android:visibility="gone"
+ android:gravity="bottom"
+ android:orientation="vertical"
+ />
+ </com.android.systemui.globalactions.ListGridLayout>
+ </LinearLayout>
+
+</com.android.systemui.globalactions.GlobalActionsGridLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
new file mode 100644
index 000000000000..0c11cd977256
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+ work around this for now with LinearLayouts. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="72dp"
+ android:layout_height="72dp"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin"
+ android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_item_side_margin"
+ android:paddingEnd="4dip"
+ android:paddingStart="4dip">
+
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:scaleType="center"
+ android:alpha="?android:attr/primaryContentAlpha"
+ />
+
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal"
+ android:paddingTop="10dp"
+ android:gravity="center"
+ android:textSize="12sp"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+
+ <TextView
+ android:id="@*android:id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal"
+ android:gravity="center"
+ android:textColor="?android:attr/textColorTertiary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_carrier.xml b/packages/SystemUI/res/layout/qs_footer_carrier.xml
new file mode 100644
index 000000000000..bd492b022e36
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_footer_carrier.xml
@@ -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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear_footer_carrier"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:layout_weight="1"
+ android:gravity="center_vertical|start"
+ android:background="@android:color/transparent"
+ android:clickable="false"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingStart="16dp" >
+
+ <include
+ layout="@layout/mobile_signal_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:visibility="gone" />
+
+ <view class="com.android.systemui.qs.QSFooterImpl$QSCarrierText"
+ android:id="@+id/qs_carrier_text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:ellipsize="marquee"
+ android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textDirection="locale"
+ android:singleLine="true" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 890bf5d8ac45..abf9e056ed22 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -42,30 +42,31 @@
android:gravity="end" >
<LinearLayout
+ android:id="@+id/qs_mobile"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical|start"
- android:paddingStart="16dp">
+ android:orientation="horizontal"
+ android:layout_marginEnd="32dp">
<include
- layout="@layout/mobile_signal_group"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
+ layout="@layout/qs_footer_carrier"
+ android:id="@+id/carrier1" />
+
+ <View
+ android:id="@+id/qs_carrier_divider"
+ android:layout_width="2dp"
+ android:layout_height="match_parent"
+ android:layout_marginTop="15dp"
+ android:layout_marginBottom="15dp"
+ android:background="?android:attr/dividerVertical"
android:visibility="gone" />
- <com.android.keyguard.CarrierText
- android:id="@+id/qs_carrier_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginEnd="32dp"
- android:ellipsize="marquee"
- android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
- android:textColor="?android:attr/textColorPrimary"
- android:textDirection="locale"
- android:singleLine="true" />
+ <include
+ layout="@layout/qs_footer_carrier"
+ android:id="@+id/carrier2"
+ android:visibility="gone"/>
</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab0bbe10c37c..a14259eca45e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -834,6 +834,18 @@
<dimen name="global_actions_panel_width">120dp</dimen>
+ <dimen name="global_actions_grid_container_bottom_margin">16dp</dimen>
+
+ <dimen name="global_actions_grid_side_margin">4dp</dimen>
+ <dimen name="global_actions_grid_separated_panel_width">104dp</dimen>
+ <dimen name="global_actions_grid_top_padding">8dp</dimen>
+ <dimen name="global_actions_grid_bottom_padding">8dp</dimen>
+ <dimen name="global_actions_grid_left_padding">4dp</dimen>
+ <dimen name="global_actions_grid_right_padding">4dp</dimen>
+
+ <dimen name="global_actions_grid_item_side_margin">12dp</dimen>
+ <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen>
+
<dimen name="global_actions_top_padding">120dp</dimen>
<dimen name="global_actions_padding">12dp</dimen>
@@ -982,8 +994,8 @@
<dimen name="bubble_view_padding">0dp</dimen>
<!-- Padding between bubbles when displayed in expanded state -->
<dimen name="bubble_padding">8dp</dimen>
- <!-- Size of the collapsed bubble -->
- <dimen name="bubble_size">56dp</dimen>
+ <!-- Size of individual bubbles. -->
+ <dimen name="individual_bubble_size">56dp</dimen>
<!-- How much to inset the icon in the circle -->
<dimen name="bubble_icon_inset">16dp</dimen>
<!-- Padding around the view displayed when the bubble is expanded -->
@@ -1000,10 +1012,20 @@
<dimen name="bubble_expanded_header_height">48dp</dimen>
<!-- Left and right padding applied to the header. -->
<dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+ <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
+ <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
+ <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
+ <dimen name="bubble_expanded_animate_y_distance">500dp</dimen>
<!-- Max width of the message bubble-->
<dimen name="bubble_message_max_width">144dp</dimen>
<!-- Min width of the message bubble -->
<dimen name="bubble_message_min_width">32dp</dimen>
<!-- Interior padding of the message bubble -->
<dimen name="bubble_message_padding">4dp</dimen>
+ <!-- Offset between bubbles in their stacked position. -->
+ <dimen name="bubble_stack_offset">5dp</dimen>
+ <!-- How far offscreen the bubble stack rests. -->
+ <dimen name="bubble_stack_offscreen">5dp</dimen>
+ <!-- How far down the screen the stack starts. -->
+ <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index bd34beac7fd6..6a6845742788 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -68,6 +68,7 @@
<item type="id" name="panel_alpha_animator_tag"/>
<item type="id" name="panel_alpha_animator_start_tag"/>
<item type="id" name="panel_alpha_animator_end_tag"/>
+ <item type="id" name="cross_fade_layer_type_changed_tag"/>
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
@@ -115,6 +116,14 @@
<item type="id" name="aod_mask_transition_progress_end_tag" />
<item type="id" name="aod_mask_transition_progress_start_tag" />
+ <!-- For saving DynamicAnimation physics animations as view tags. -->
+ <item type="id" name="translation_x_dynamicanimation_tag"/>
+ <item type="id" name="translation_y_dynamicanimation_tag"/>
+ <item type="id" name="translation_z_dynamicanimation_tag"/>
+ <item type="id" name="alpha_dynamicanimation_tag"/>
+ <item type="id" name="scale_x_dynamicanimation_tag"/>
+ <item type="id" name="scale_y_dynamicanimation_tag"/>
+
<!-- Global Actions Menu -->
<item type="id" name="global_actions_view" />
</resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index fd7a10500f36..e8fabf5a07f1 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -21,4 +21,10 @@
0) as we can allow the carrier text to stretch as far as needed in the QS footer. -->
<integer name="qs_footer_actions_width">-2</integer>
<integer name="qs_footer_actions_weight">0</integer>
+
+ <!-- Maximum number of bubbles to render and animate at one time. While the animations used are
+ lightweight translation animations, this number can be reduced on lower end devices if any
+ performance issues arise. -->
+ <integer name="bubbles_max_rendered">5</integer>
+
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index b7d51978fab2..adcb7a125e80 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -17,29 +17,14 @@
package com.android.keyguard;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.TypedArray;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.method.SingleLineTransformationMethod;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.TextView;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.settingslib.WirelessUtils;
-
-import java.util.List;
import java.util.Locale;
-import java.util.Objects;
public class CarrierText extends TextView {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -47,77 +32,30 @@ public class CarrierText extends TextView {
private static CharSequence mSeparator;
- private final boolean mIsEmergencyCallCapable;
-
- private boolean mTelephonyCapable;
-
private boolean mShowMissingSim;
private boolean mShowAirplaneMode;
+ private boolean mShouldMarquee;
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- private WifiManager mWifiManager;
-
- private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
-
- private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshCarrierInfo() {
- if (DEBUG) Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
- + Boolean.toString(mTelephonyCapable));
- updateCarrierText();
- }
-
- public void onFinishedGoingToSleep(int why) {
- setSelected(false);
- };
-
- public void onStartedWakingUp() {
- setSelected(true);
- };
+ private CarrierTextController mCarrierTextController;
- @Override
- public void onTelephonyCapable(boolean capable) {
- if (DEBUG) Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
- + Boolean.toString(capable));
- mTelephonyCapable = capable;
- updateCarrierText();
- }
-
- public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
- if (slotId < 0) {
- Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
- + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
- return;
- }
+ private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
+ new CarrierTextController.CarrierTextCallback() {
+ @Override
+ public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+ setText(info.carrierText);
+ }
- if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState));
- if (getStatusForIccState(simState) == StatusMode.SimIoError) {
- mSimErrorState[slotId] = true;
- updateCarrierText();
- } else if (mSimErrorState[slotId]) {
- mSimErrorState[slotId] = false;
- updateCarrierText();
- }
- }
- };
+ @Override
+ public void startedGoingToSleep() {
+ setSelected(false);
+ }
- /**
- * The status of this lock screen. Primarily used for widgets on LockScreen.
- */
- private static enum StatusMode {
- Normal, // Normal case (sim card present, it's not locked)
- NetworkLocked, // SIM card is 'network locked'.
- SimMissing, // SIM card is missing.
- SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
- SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
- SimLocked, // SIM card is currently locked
- SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
- SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
- SimIoError, // SIM card is faulty
- SimUnknown // SIM card is unknown
- }
+ @Override
+ public void finishedWakingUp() {
+ setSelected(true);
+ }
+ };
public CarrierText(Context context) {
this(context, null);
@@ -125,8 +63,6 @@ public class CarrierText extends TextView {
public CarrierText(Context context, AttributeSet attrs) {
super(context, attrs);
- mIsEmergencyCallCapable = context.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
boolean useAllCaps;
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.CarrierText, 0, 0);
@@ -138,132 +74,6 @@ public class CarrierText extends TextView {
a.recycle();
}
setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
-
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- }
-
- /**
- * Checks if there are faulty cards. Adds the text depending on the slot of the card
- * @param text: current carrier text based on the sim state
- * @param noSims: whether a valid sim card is inserted
- * @return text
- */
- private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
- final CharSequence carrier = "";
- CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
- IccCardConstants.State.CARD_IO_ERROR, carrier);
- for (int index = 0; index < mSimErrorState.length; index++) {
- if (mSimErrorState[index]) {
- // In the case when no sim cards are detected but a faulty card is inserted
- // overwrite the text and only show "Invalid card"
- if (noSims) {
- return concatenate(carrierTextForSimIOError,
- getContext().getText(com.android.internal.R.string.emergency_calls_only));
- } else if (index == 0) {
- // prepend "Invalid card" when faulty card is inserted in slot 0
- text = concatenate(carrierTextForSimIOError, text);
- } else {
- // concatenate "Invalid card" when faulty card is inserted in slot 1
- text = concatenate(text, carrierTextForSimIOError);
- }
- }
- }
- return text;
- }
-
- protected void updateCarrierText() {
- boolean allSimsMissing = true;
- boolean anySimReadyAndInService = false;
- CharSequence displayText = null;
-
- List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
- final int N = subs.size();
- if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
- for (int i = 0; i < N; i++) {
- int subId = subs.get(i).getSubscriptionId();
- State simState = mKeyguardUpdateMonitor.getSimState(subId);
- CharSequence carrierName = subs.get(i).getCarrierName();
- CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
- if (DEBUG) {
- Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
- }
- if (carrierTextForSimState != null) {
- allSimsMissing = false;
- displayText = concatenate(displayText, carrierTextForSimState);
- }
- if (simState == IccCardConstants.State.READY) {
- ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
- if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
- // hack for WFC (IWLAN) not turning off immediately once
- // Wi-Fi is disassociated or disabled
- if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
- || (mWifiManager.isWifiEnabled()
- && mWifiManager.getConnectionInfo() != null
- && mWifiManager.getConnectionInfo().getBSSID() != null)) {
- if (DEBUG) {
- Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
- }
- anySimReadyAndInService = true;
- }
- }
- }
- }
- if (allSimsMissing) {
- if (N != 0) {
- // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
- // This depends on mPlmn containing the text "Emergency calls only" when the radio
- // has some connectivity. Otherwise, it should be null or empty and just show
- // "No SIM card"
- // Grab the first subscripton, because they all should contain the emergency text,
- // described above.
- displayText = makeCarrierStringOnEmergencyCapable(
- getMissingSimMessage(), subs.get(0).getCarrierName());
- } else {
- // We don't have a SubscriptionInfo to get the emergency calls only from.
- // Grab it from the old sticky broadcast if possible instead. We can use it
- // here because no subscriptions are active, so we don't have
- // to worry about MSIM clashing.
- CharSequence text =
- getContext().getText(com.android.internal.R.string.emergency_calls_only);
- Intent i = getContext().registerReceiver(null,
- new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
- if (i != null) {
- String spn = "";
- String plmn = "";
- if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
- spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
- }
- if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
- plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
- }
- if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
- if (Objects.equals(plmn, spn)) {
- text = plmn;
- } else {
- text = concatenate(plmn, spn);
- }
- }
- displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
- }
- }
-
- displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
- // APM (airplane mode) != no carrier state. There are carrier services
- // (e.g. WFC = Wi-Fi calling) which may operate in APM.
- if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
- displayText = getAirplaneModeMessage();
- }
- setText(displayText);
- }
-
- private String getMissingSimMessage() {
- return mShowMissingSim && mTelephonyCapable
- ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
- }
-
- private String getAirplaneModeMessage() {
- return mShowAirplaneMode
- ? getContext().getString(R.string.airplane_mode) : "";
}
@Override
@@ -271,36 +81,27 @@ public class CarrierText extends TextView {
super.onFinishInflate();
mSeparator = getResources().getString(
com.android.internal.R.string.kg_text_message_separator);
- boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
- setSelected(shouldMarquee); // Allow marquee to work.
+ mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
+ mShowMissingSim);
+ mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+ setSelected(mShouldMarquee); // Allow marquee to work.
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (ConnectivityManager.from(mContext).isNetworkSupported(
- ConnectivityManager.TYPE_MOBILE)) {
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- mKeyguardUpdateMonitor.registerCallback(mCallback);
- } else {
- // Don't listen and clear out the text when the device isn't a phone.
- mKeyguardUpdateMonitor = null;
- setText("");
- }
+ mCarrierTextController.setListening(mCarrierTextCallback);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mKeyguardUpdateMonitor != null) {
- mKeyguardUpdateMonitor.removeCallback(mCallback);
- }
+ mCarrierTextController.setListening(null);
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
-
// Only show marquee when visible
if (visibility == VISIBLE) {
setEllipsize(TextUtils.TruncateAt.MARQUEE);
@@ -309,167 +110,6 @@ public class CarrierText extends TextView {
}
}
- /**
- * Top-level function for creating carrier text. Makes text based on simState, PLMN
- * and SPN as well as device capabilities, such as being emergency call capable.
- *
- * @param simState
- * @param text
- * @param spn
- * @return Carrier text if not in missing state, null otherwise.
- */
- private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
- CharSequence text) {
- CharSequence carrierText = null;
- StatusMode status = getStatusForIccState(simState);
- switch (status) {
- case Normal:
- carrierText = text;
- break;
-
- case SimNotReady:
- // Null is reserved for denoting missing, in this case we have nothing to display.
- carrierText = ""; // nothing to display yet.
- break;
-
- case NetworkLocked:
- carrierText = makeCarrierStringOnEmergencyCapable(
- mContext.getText(R.string.keyguard_network_locked_message), text);
- break;
-
- case SimMissing:
- carrierText = null;
- break;
-
- case SimPermDisabled:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(
- R.string.keyguard_permanent_disabled_sim_message_short),
- text);
- break;
-
- case SimMissingLocked:
- carrierText = null;
- break;
-
- case SimLocked:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_sim_locked_message),
- text);
- break;
-
- case SimPukLocked:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_sim_puk_locked_message),
- text);
- break;
- case SimIoError:
- carrierText = makeCarrierStringOnEmergencyCapable(
- getContext().getText(R.string.keyguard_sim_error_message_short),
- text);
- break;
- case SimUnknown:
- carrierText = null;
- break;
- }
-
- return carrierText;
- }
-
- /*
- * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
- */
- private CharSequence makeCarrierStringOnEmergencyCapable(
- CharSequence simMessage, CharSequence emergencyCallMessage) {
- if (mIsEmergencyCallCapable) {
- return concatenate(simMessage, emergencyCallMessage);
- }
- return simMessage;
- }
-
- /**
- * Determine the current status of the lock screen given the SIM state and other stuff.
- */
- private StatusMode getStatusForIccState(IccCardConstants.State simState) {
- // Since reading the SIM may take a while, we assume it is present until told otherwise.
- if (simState == null) {
- return StatusMode.Normal;
- }
-
- final boolean missingAndNotProvisioned =
- !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
- && (simState == IccCardConstants.State.ABSENT ||
- simState == IccCardConstants.State.PERM_DISABLED);
-
- // Assume we're NETWORK_LOCKED if not provisioned
- simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
- switch (simState) {
- case ABSENT:
- return StatusMode.SimMissing;
- case NETWORK_LOCKED:
- return StatusMode.SimMissingLocked;
- case NOT_READY:
- return StatusMode.SimNotReady;
- case PIN_REQUIRED:
- return StatusMode.SimLocked;
- case PUK_REQUIRED:
- return StatusMode.SimPukLocked;
- case READY:
- return StatusMode.Normal;
- case PERM_DISABLED:
- return StatusMode.SimPermDisabled;
- case UNKNOWN:
- return StatusMode.SimUnknown;
- case CARD_IO_ERROR:
- return StatusMode.SimIoError;
- }
- return StatusMode.SimUnknown;
- }
-
- private static CharSequence concatenate(CharSequence plmn, CharSequence spn) {
- final boolean plmnValid = !TextUtils.isEmpty(plmn);
- final boolean spnValid = !TextUtils.isEmpty(spn);
- if (plmnValid && spnValid) {
- return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString();
- } else if (plmnValid) {
- return plmn;
- } else if (spnValid) {
- return spn;
- } else {
- return "";
- }
- }
-
- private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
- String plmn, String spn) {
- int carrierHelpTextId = 0;
- StatusMode status = getStatusForIccState(simState);
- switch (status) {
- case NetworkLocked:
- carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
- break;
-
- case SimMissing:
- carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
- break;
-
- case SimPermDisabled:
- carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
- break;
-
- case SimMissingLocked:
- carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
- break;
-
- case Normal:
- case SimLocked:
- case SimPukLocked:
- break;
- }
-
- return mContext.getText(carrierHelpTextId);
- }
-
private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
private final Locale mLocale;
private final boolean mAllCaps;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
new file mode 100644
index 000000000000..2ce69650b65c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -0,0 +1,518 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Controller that generates text including the carrier names and/or the status of all the SIM
+ * interfaces in the device. Through a callback, the updates can be retrieved either as a list or
+ * separated by a given separator {@link CharSequence}.
+ */
+public class CarrierTextController {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "CarrierTextController";
+
+ private final boolean mIsEmergencyCallCapable;
+ private boolean mTelephonyCapable;
+ private boolean mShowMissingSim;
+ private boolean mShowAirplaneMode;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private WifiManager mWifiManager;
+ private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
+ private CarrierTextCallback mCarrierTextCallback;
+ private Context mContext;
+ private CharSequence mSeparator;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
+ private final WakefulnessLifecycle.Observer mWakefulnessObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onFinishedWakingUp() {
+ mCarrierTextCallback.finishedWakingUp();
+ }
+
+ @Override
+ public void onStartedGoingToSleep() {
+ mCarrierTextCallback.startedGoingToSleep();
+ }
+ };
+
+ private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ if (DEBUG) {
+ Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
+ + Boolean.toString(mTelephonyCapable));
+ }
+ updateCarrierText();
+ }
+
+ @Override
+ public void onTelephonyCapable(boolean capable) {
+ if (DEBUG) {
+ Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
+ + Boolean.toString(capable));
+ }
+ mTelephonyCapable = capable;
+ updateCarrierText();
+ }
+
+ public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+ if (slotId < 0) {
+ Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId
+ + " mTelephonyCapable: " + Boolean.toString(mTelephonyCapable));
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+ if (getStatusForIccState(simState) == CarrierTextController.StatusMode.SimIoError) {
+ mSimErrorState[slotId] = true;
+ updateCarrierText();
+ } else if (mSimErrorState[slotId]) {
+ mSimErrorState[slotId] = false;
+ updateCarrierText();
+ }
+ }
+ };
+
+ /**
+ * The status of this lock screen. Primarily used for widgets on LockScreen.
+ */
+ private enum StatusMode {
+ Normal, // Normal case (sim card present, it's not locked)
+ NetworkLocked, // SIM card is 'network locked'.
+ SimMissing, // SIM card is missing.
+ SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+ SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+ SimLocked, // SIM card is currently locked
+ SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+ SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+ SimIoError, // SIM card is faulty
+ SimUnknown // SIM card is unknown
+ }
+
+ /**
+ * Controller that provides updates on text with carriers names or SIM status.
+ * Used by {@link CarrierText}.
+ *
+ * @param separator Separator between different parts of the text
+ */
+ public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
+ boolean showMissingSim) {
+ mContext = context;
+ mIsEmergencyCallCapable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable);
+
+ mShowAirplaneMode = showAirplaneMode;
+ mShowMissingSim = showMissingSim;
+
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mSeparator = separator;
+ mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+ }
+
+ /**
+ * Checks if there are faulty cards. Adds the text depending on the slot of the card
+ *
+ * @param text: current carrier text based on the sim state
+ * @param noSims: whether a valid sim card is inserted
+ * @return text
+ */
+ private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
+ final CharSequence carrier = "";
+ CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+ IccCardConstants.State.CARD_IO_ERROR, carrier);
+ for (int index = 0; index < mSimErrorState.length; index++) {
+ if (mSimErrorState[index]) {
+ // In the case when no sim cards are detected but a faulty card is inserted
+ // overwrite the text and only show "Invalid card"
+ if (noSims) {
+ return concatenate(carrierTextForSimIOError,
+ getContext().getText(
+ com.android.internal.R.string.emergency_calls_only),
+ mSeparator);
+ } else if (index == 0) {
+ // prepend "Invalid card" when faulty card is inserted in slot 0
+ text = concatenate(carrierTextForSimIOError, text, mSeparator);
+ } else {
+ // concatenate "Invalid card" when faulty card is inserted in slot 1
+ text = concatenate(text, carrierTextForSimIOError, mSeparator);
+ }
+ }
+ }
+ return text;
+ }
+
+ /**
+ * Sets the listening status of this controller. If the callback is null, it is set to
+ * not listening
+ *
+ * @param callback Callback to provide text updates
+ */
+ public void setListening(CarrierTextCallback callback) {
+ if (callback != null) {
+ mCarrierTextCallback = callback;
+ if (ConnectivityManager.from(mContext).isNetworkSupported(
+ ConnectivityManager.TYPE_MOBILE)) {
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardUpdateMonitor.registerCallback(mCallback);
+ mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
+ } else {
+ // Don't listen and clear out the text when the device isn't a phone.
+ mKeyguardUpdateMonitor = null;
+ callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
+ }
+ } else {
+ mCarrierTextCallback = null;
+ if (mKeyguardUpdateMonitor != null) {
+ mKeyguardUpdateMonitor.removeCallback(mCallback);
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
+ }
+ }
+ }
+
+ protected void updateCarrierText() {
+ boolean allSimsMissing = true;
+ boolean anySimReadyAndInService = false;
+ CharSequence displayText = null;
+
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ final int numSubs = subs.size();
+ final int[] subsIds = new int[numSubs];
+ if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+ for (int i = 0; i < numSubs; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ subsIds[i] = subId;
+ IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+ if (DEBUG) {
+ Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+ }
+ if (carrierTextForSimState != null) {
+ allSimsMissing = false;
+ displayText = concatenate(displayText, carrierTextForSimState, mSeparator);
+ }
+ if (simState == IccCardConstants.State.READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+ if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+ // hack for WFC (IWLAN) not turning off immediately once
+ // Wi-Fi is disassociated or disabled
+ if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+ || (mWifiManager.isWifiEnabled()
+ && mWifiManager.getConnectionInfo() != null
+ && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+ if (DEBUG) {
+ Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+ }
+ anySimReadyAndInService = true;
+ }
+ }
+ }
+ }
+ if (allSimsMissing) {
+ if (numSubs != 0) {
+ // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+ // This depends on mPlmn containing the text "Emergency calls only" when the radio
+ // has some connectivity. Otherwise, it should be null or empty and just show
+ // "No SIM card"
+ // Grab the first subscripton, because they all should contain the emergency text,
+ // described above.
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getMissingSimMessage(), subs.get(0).getCarrierName());
+ } else {
+ // We don't have a SubscriptionInfo to get the emergency calls only from.
+ // Grab it from the old sticky broadcast if possible instead. We can use it
+ // here because no subscriptions are active, so we don't have
+ // to worry about MSIM clashing.
+ CharSequence text =
+ getContext().getText(com.android.internal.R.string.emergency_calls_only);
+ Intent i = getContext().registerReceiver(null,
+ new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
+ if (i != null) {
+ String spn = "";
+ String plmn = "";
+ if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
+ spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
+ }
+ if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
+ plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+ }
+ if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+ if (Objects.equals(plmn, spn)) {
+ text = plmn;
+ } else {
+ text = concatenate(plmn, spn, mSeparator);
+ }
+ }
+ displayText = makeCarrierStringOnEmergencyCapable(getMissingSimMessage(), text);
+ }
+ }
+
+ displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
+ // APM (airplane mode) != no carrier state. There are carrier services
+ // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+ if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+ displayText = getAirplaneModeMessage();
+ }
+
+ if (mCarrierTextCallback != null) {
+ mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo(
+ displayText,
+ displayText.toString().split(mSeparator.toString()),
+ anySimReadyAndInService,
+ subsIds));
+ }
+
+ }
+
+ private Context getContext() {
+ return mContext;
+ }
+
+ private String getMissingSimMessage() {
+ return mShowMissingSim && mTelephonyCapable
+ ? getContext().getString(R.string.keyguard_missing_sim_message_short) : "";
+ }
+
+ private String getAirplaneModeMessage() {
+ return mShowAirplaneMode
+ ? getContext().getString(R.string.airplane_mode) : "";
+ }
+
+ /**
+ * Top-level function for creating carrier text. Makes text based on simState, PLMN
+ * and SPN as well as device capabilities, such as being emergency call capable.
+ *
+ * @return Carrier text if not in missing state, null otherwise.
+ */
+ private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
+ CharSequence text) {
+ CharSequence carrierText = null;
+ CarrierTextController.StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case Normal:
+ carrierText = text;
+ break;
+
+ case SimNotReady:
+ // Null is reserved for denoting missing, in this case we have nothing to display.
+ carrierText = ""; // nothing to display yet.
+ break;
+
+ case NetworkLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ mContext.getText(R.string.keyguard_network_locked_message), text);
+ break;
+
+ case SimMissing:
+ carrierText = null;
+ break;
+
+ case SimPermDisabled:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(
+ R.string.keyguard_permanent_disabled_sim_message_short),
+ text);
+ break;
+
+ case SimMissingLocked:
+ carrierText = null;
+ break;
+
+ case SimLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_locked_message),
+ text);
+ break;
+
+ case SimPukLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_puk_locked_message),
+ text);
+ break;
+ case SimIoError:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_error_message_short),
+ text);
+ break;
+ case SimUnknown:
+ carrierText = null;
+ break;
+ }
+
+ return carrierText;
+ }
+
+ /*
+ * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+ */
+ private CharSequence makeCarrierStringOnEmergencyCapable(
+ CharSequence simMessage, CharSequence emergencyCallMessage) {
+ if (mIsEmergencyCallCapable) {
+ return concatenate(simMessage, emergencyCallMessage, mSeparator);
+ }
+ return simMessage;
+ }
+
+ /**
+ * Determine the current status of the lock screen given the SIM state and other stuff.
+ */
+ private CarrierTextController.StatusMode getStatusForIccState(IccCardConstants.State simState) {
+ // Since reading the SIM may take a while, we assume it is present until told otherwise.
+ if (simState == null) {
+ return CarrierTextController.StatusMode.Normal;
+ }
+
+ final boolean missingAndNotProvisioned =
+ !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
+ && (simState == IccCardConstants.State.ABSENT
+ || simState == IccCardConstants.State.PERM_DISABLED);
+
+ // Assume we're NETWORK_LOCKED if not provisioned
+ simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
+ switch (simState) {
+ case ABSENT:
+ return CarrierTextController.StatusMode.SimMissing;
+ case NETWORK_LOCKED:
+ return CarrierTextController.StatusMode.SimMissingLocked;
+ case NOT_READY:
+ return CarrierTextController.StatusMode.SimNotReady;
+ case PIN_REQUIRED:
+ return CarrierTextController.StatusMode.SimLocked;
+ case PUK_REQUIRED:
+ return CarrierTextController.StatusMode.SimPukLocked;
+ case READY:
+ return CarrierTextController.StatusMode.Normal;
+ case PERM_DISABLED:
+ return CarrierTextController.StatusMode.SimPermDisabled;
+ case UNKNOWN:
+ return CarrierTextController.StatusMode.SimUnknown;
+ case CARD_IO_ERROR:
+ return CarrierTextController.StatusMode.SimIoError;
+ }
+ return CarrierTextController.StatusMode.SimUnknown;
+ }
+
+ private static CharSequence concatenate(CharSequence plmn, CharSequence spn,
+ CharSequence separator) {
+ final boolean plmnValid = !TextUtils.isEmpty(plmn);
+ final boolean spnValid = !TextUtils.isEmpty(spn);
+ if (plmnValid && spnValid) {
+ return new StringBuilder().append(plmn).append(separator).append(spn).toString();
+ } else if (plmnValid) {
+ return plmn;
+ } else if (spnValid) {
+ return spn;
+ } else {
+ return "";
+ }
+ }
+
+ private static List<CharSequence> append(List<CharSequence> list, CharSequence string) {
+ if (!TextUtils.isEmpty(string)) {
+ list.add(string);
+ }
+ return list;
+ }
+
+ private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
+ String plmn, String spn) {
+ int carrierHelpTextId = 0;
+ CarrierTextController.StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case NetworkLocked:
+ carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+ break;
+
+ case SimMissing:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+ break;
+
+ case SimPermDisabled:
+ carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+ break;
+
+ case SimMissingLocked:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+ break;
+
+ case Normal:
+ case SimLocked:
+ case SimPukLocked:
+ break;
+ }
+
+ return mContext.getText(carrierHelpTextId);
+ }
+
+ /**
+ * Data structure for passing information to CarrierTextController subscribers
+ */
+ public static final class CarrierTextCallbackInfo {
+ public final CharSequence carrierText;
+ public final CharSequence[] listOfCarriers;
+ public final boolean anySimReady;
+ public final int[] subscriptionIds;
+
+ CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+ boolean anySimReady, int[] subscriptionIds) {
+ this.carrierText = carrierText;
+ this.listOfCarriers = listOfCarriers;
+ this.anySimReady = anySimReady;
+ this.subscriptionIds = subscriptionIds;
+ }
+ }
+
+ /**
+ * Callback to communicate to Views
+ */
+ public interface CarrierTextCallback {
+ /**
+ * Provides updated carrier information.
+ */
+ default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
+
+ /**
+ * Notifies the View that the device is going to sleep
+ */
+ default void startedGoingToSleep() {};
+
+ /**
+ * Notifies the View that the device finished waking up
+ */
+ default void finishedWakingUp() {};
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d99f234c26c8..fece94e69a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -44,6 +44,7 @@ import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.PowerUI;
+import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AmbientPulseManager;
@@ -278,6 +279,7 @@ public class Dependency extends SystemUI {
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
+ @Inject Lazy<PrivacyItemController> mPrivacyItemController;
@Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
@Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
@Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
@@ -452,6 +454,8 @@ public class Dependency extends SystemUI {
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
mProviders.put(ClockManager.class, mClockManager::get);
+ mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
+
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index 0c7a9a9fffd2..85265f458370 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -26,11 +26,11 @@ import android.widget.LinearLayout;
* Layout class representing the Global Actions menu which appears when the power button is held.
*/
public abstract class MultiListLayout extends LinearLayout {
- boolean mHasOutsideTouch;
- boolean mHasSeparatedView;
+ protected boolean mHasOutsideTouch;
+ protected boolean mHasSeparatedView;
- int mExpectedSeparatedItemCount;
- int mExpectedListItemCount;
+ protected int mExpectedSeparatedItemCount;
+ protected int mExpectedListItemCount;
public MultiListLayout(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 92d3cc1ae34f..36a813b914d5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -57,7 +57,7 @@ public class BadgedImageView extends ImageView {
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setScaleType(ScaleType.CENTER_CROP);
- mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
mDotRenderer = new BadgeRenderer(mIconSize);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a457deed7ba4..b7bee30dc640 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -18,9 +18,8 @@ package com.android.systemui.bubbles;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
@@ -229,10 +228,6 @@ public class BubbleController {
}
mStackView.stackDismissed();
- // Reset the position of the stack (TODO - or should we save / respect last user position?)
- Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
- mStackView.setPosition(startPoint.x, startPoint.y);
-
updateVisibility();
mNotificationEntryManager.updateNotifications();
}
@@ -249,16 +244,14 @@ public class BubbleController {
BubbleView bubble = mBubbles.get(notif.key);
mStackView.updateBubble(bubble, notif, updatePosition);
} else {
- boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
if (mStackView == null) {
- setPosition = true;
mStackView = new BubbleStackView(mContext);
ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
// XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
// between bubble and the shade
int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
sbv.addView(mStackView, bubblePosition,
- new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
@@ -273,11 +266,6 @@ public class BubbleController {
}
mBubbles.put(bubble.getKey(), bubble);
mStackView.addBubble(bubble);
- if (setPosition) {
- // Need to add the bubble to the stack before we can know the width
- Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
- mStackView.setPosition(startPoint.x, startPoint.y);
- }
}
updateVisibility();
}
@@ -423,24 +411,6 @@ public class BubbleController {
return mStackView;
}
- // TODO: factor in PIP location / maybe last place user had it
- /**
- * Gets an appropriate starting point to position the bubble stack.
- */
- private static Point getStartPoint(int size, Point displaySize) {
- final int x = displaySize.x - size + EDGE_OVERLAP;
- final int y = displaySize.y / 4;
- return new Point(x, y);
- }
-
- /**
- * Gets an appropriate position for the bubble when the stack is expanded.
- */
- static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
- // Same place for now..
- return new Point(EDGE_OVERLAP, size);
- }
-
/**
* Whether the notification has been developer configured to bubble and is allowed by the user.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
deleted file mode 100644
index c1063fa54ff2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
-
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView;
-
-import java.util.Arrays;
-
-/**
- * Math and animators to move bubbles around the screen.
- *
- * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip
- * movements can be unified maybe?
- */
-public class BubbleMovementHelper {
-
- private static final int MAGNET_ANIM_TIME = 150;
- public static final int EDGE_OVERLAP = 0;
-
- private Context mContext;
- private Point mDisplaySize;
-
- public BubbleMovementHelper(Context context) {
- mContext = context;
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mDisplaySize = new Point();
- wm.getDefaultDisplay().getSize(mDisplaySize);
- }
-
- /**
- * @return the distance between the two provided points.
- */
- static double distance(Point p1, Point p2) {
- return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
- }
-
- /**
- * @return the y value of a line defined by y = mx+b
- */
- static float findY(float m, float b, float x) {
- return (m * x) + b;
- }
-
- /**
- * @return the x value of a line defined by y = mx+b
- */
- static float findX(float m, float b, float y) {
- return (y - b) / m;
- }
-
- /**
- * Determines a point on the edge of the screen based on the velocity and position.
- */
- public Point getPointOnEdge(View bv, Point p, float velX, float velY) {
- // Find the slope and the y-intercept
- velX = velX == 0 ? 1 : velX;
- final float m = velY / velX;
- final float b = p.y - m * p.x;
-
- // There are two lines it can intersect, find the two points
- Point pointHoriz = new Point();
- Point pointVert = new Point();
-
- if (velX > 0) {
- // right
- pointHoriz.x = mDisplaySize.x;
- pointHoriz.y = (int) findY(m, b, mDisplaySize.x);
- } else {
- // left
- pointHoriz.x = EDGE_OVERLAP;
- pointHoriz.y = (int) findY(m, b, 0);
- }
- if (velY > 0) {
- // bottom
- pointVert.x = (int) findX(m, b, mDisplaySize.y);
- pointVert.y = mDisplaySize.y - getNavBarHeight();
- } else {
- // top
- pointVert.x = (int) findX(m, b, 0);
- pointVert.y = EDGE_OVERLAP;
- }
-
- // Use the point that's closest to the start position
- final double distanceToVertPoint = distance(p, pointVert);
- final double distanceToHorizPoint = distance(p, pointHoriz);
- boolean useVert = distanceToVertPoint < distanceToHorizPoint;
- // Check if we're being flung along the current edge, use opposite point in this case
- // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but
- // this works well enough for now
- if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) {
- // Flinging along left or right edge, favor vert edge
- useVert = true;
-
- } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) {
- // Flinging along top or bottom edge
- useVert = false;
- }
-
- if (useVert) {
- pointVert.x = capX(pointVert.x, bv);
- pointVert.y = capY(pointVert.y, bv);
- return pointVert;
-
- }
- pointHoriz.x = capX(pointHoriz.x, bv);
- pointHoriz.y = capY(pointHoriz.y, bv);
- return pointHoriz;
- }
-
- /**
- * @return whether the view is on a side edge of the screen (i.e. left or right).
- */
- public boolean onSideEdge(View fv, Point p) {
- return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x
- - EDGE_OVERLAP
- || p.x >= EDGE_OVERLAP;
- }
-
- /**
- * @return whether the view is on a top or bottom edge of the screen.
- */
- public boolean onTopBotEdge(View bv, Point p) {
- return p.y >= getStatusBarHeight() + EDGE_OVERLAP
- || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y
- - EDGE_OVERLAP;
- }
-
- /**
- * @return constrained x value based on screen size and how much a view can overlap with a side
- * edge.
- */
- public int capX(float x, View bv) {
- // Floating things can't stick to top or bottom edges, so figure out if it's closer to
- // left or right and just use that side + the overlap.
- final float centerX = x + bv.getWidth() / 2;
- if (centerX > mDisplaySize.x / 2) {
- // Right side
- return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP;
- } else {
- // Left side
- return EDGE_OVERLAP;
- }
- }
-
- /**
- * @return constrained y value based on screen size and how much a view can overlap with a top
- * or bottom edge.
- */
- public int capY(float y, View bv) {
- final int height = bv.getHeight();
- if (y < getStatusBarHeight() + EDGE_OVERLAP) {
- return getStatusBarHeight() + EDGE_OVERLAP;
- }
- if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) {
- return mDisplaySize.y - height - EDGE_OVERLAP;
- }
- return (int) y;
- }
-
- /**
- * Animation to translate the provided view.
- */
- public AnimatorSet animateMagnetTo(final BubbleStackView bv) {
- Point pos = bv.getPosition();
-
- // Find the distance to each edge
- final int leftDistance = pos.x;
- final int rightDistance = mDisplaySize.x - leftDistance;
- final int topDistance = pos.y;
- final int botDistance = mDisplaySize.y - topDistance;
-
- int smallest;
- // Find the closest one
- int[] distances = {
- leftDistance, rightDistance, topDistance, botDistance
- };
- Arrays.sort(distances);
- smallest = distances[0];
-
- // Animate to the closest edge
- Point p = new Point();
- if (smallest == leftDistance) {
- p.x = capX(EDGE_OVERLAP, bv);
- p.y = capY(topDistance, bv);
- }
- if (smallest == rightDistance) {
- p.x = capX(mDisplaySize.x, bv);
- p.y = capY(topDistance, bv);
- }
- if (smallest == topDistance) {
- p.x = capX(leftDistance, bv);
- p.y = capY(0, bv);
- }
- if (smallest == botDistance) {
- p.x = capX(leftDistance, bv);
- p.y = capY(mDisplaySize.y, bv);
- }
- return getTranslateAnim(bv, p, MAGNET_ANIM_TIME);
- }
-
- /**
- * Animation to fling the provided view.
- */
- public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) {
- Point pos = bv.getPosition();
- Point endPos = getPointOnEdge(bv, pos, velX, velY);
- endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv));
- final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2)
- + Math.pow(endPos.y - pos.y, 2));
- final float sumVel = Math.abs(velX) + Math.abs(velY);
- final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50);
- return getTranslateAnim(bv, endPos, duration);
- }
-
- /**
- * Animation to translate the provided view.
- */
- public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) {
- return getTranslateAnim(v, p, duration, 0);
- }
-
- /**
- * Animation to translate the provided view.
- */
- public AnimatorSet getTranslateAnim(final FloatingView v, Point p,
- int duration, int startDelay) {
- return getTranslateAnim(v, p, duration, startDelay, null);
- }
-
- /**
- * Animation to translate the provided view.
- *
- * @param v the view to translate.
- * @param p the point to translate to.
- * @param duration the duration of the animation.
- * @param startDelay the start delay of the animation.
- * @param listener the listener to add to the animation.
- *
- * @return the animation.
- */
- public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration,
- int startDelay, AnimatorListener listener) {
- Point curPos = v.getPosition();
- final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x);
- animX.setDuration(duration);
- animX.setStartDelay(startDelay);
- animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- v.setPositionX((int) value);
- }
- });
-
- final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y);
- animY.setDuration(duration);
- animY.setStartDelay(startDelay);
- animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- v.setPositionY((int) value);
- }
- });
- if (listener != null) {
- animY.addListener(listener);
- }
-
- AnimatorSet set = new AnimatorSet();
- set.playTogether(animX, animY);
- set.setInterpolator(FAST_OUT_SLOW_IN);
- return set;
- }
-
-
- // TODO -- now that this is in system we should be able to get these better, but ultimately
- // makes more sense to move to movement bounds style a la PIP
- /**
- * Returns the status bar height.
- */
- public int getStatusBarHeight() {
- Resources res = mContext.getResources();
- int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
- if (resourceId > 0) {
- return res.getDimensionPixelSize(resourceId);
- }
- return 0;
- }
-
- /**
- * Returns the status bar height.
- */
- public int getNavBarHeight() {
- Resources res = mContext.getResources();
- int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
- if (resourceId > 0) {
- return res.getDimensionPixelSize(resourceId);
- }
- return 0;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index dcd121bdb239..b584f6781796 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,59 +16,89 @@
package com.android.systemui.bubbles;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.app.ActivityView;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
+import com.android.systemui.bubbles.animation.ExpandedAnimationController;
+import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
+import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
-import com.android.systemui.statusbar.notification.stack.ViewState;
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
*/
public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
-
private static final String TAG = "BubbleStackView";
+
+ /**
+ * Friction applied to fling animations. Since the stack must land on one of the sides of the
+ * screen, we want less friction horizontally so that the stack has a better chance of making it
+ * to the side without needing a spring.
+ */
+ private static final float FLING_FRICTION_X = 1.15f;
+ private static final float FLING_FRICTION_Y = 1.5f;
+
+ /**
+ * Damping ratio to use for the stack spring animation used to spring the stack to its final
+ * position after a fling.
+ */
+ private static final float SPRING_DAMPING_RATIO = 0.85f;
+
+ /**
+ * Minimum fling velocity required to trigger moving the stack from one side of the screen to
+ * the other.
+ */
+ private static final float ESCAPE_VELOCITY = 750f;
+
private Point mDisplaySize;
- private FrameLayout mBubbleContainer;
+ private final SpringAnimation mExpandedViewXAnim;
+ private final SpringAnimation mExpandedViewYAnim;
+
+ private PhysicsAnimationLayout mBubbleContainer;
+ private StackAnimationController mStackAnimationController;
+ private ExpandedAnimationController mExpandedAnimationController;
+
private BubbleExpandedViewContainer mExpandedViewContainer;
private int mBubbleSize;
private int mBubblePadding;
+ private int mExpandedAnimateXDistance;
+ private int mExpandedAnimateYDistance;
private boolean mIsExpanded;
private int mExpandedBubbleHeight;
private BubbleTouchHandler mTouchHandler;
private BubbleView mExpandedBubble;
- private Point mCollapsedPosition;
private BubbleController.BubbleExpandListener mExpandListener;
private boolean mViewUpdatedRequested = false;
@@ -110,8 +140,12 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
setOnTouchListener(mTouchHandler);
Resources res = getResources();
- mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
+ mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mExpandedAnimateXDistance =
+ res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
+ mExpandedAnimateYDistance =
+ res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance);
mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mDisplaySize = new Point();
@@ -120,6 +154,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+
+ mStackAnimationController = new StackAnimationController();
+ mExpandedAnimationController = new ExpandedAnimationController();
+
+ mBubbleContainer = new PhysicsAnimationLayout(context);
+ mBubbleContainer.setMaxRenderedChildren(
+ getResources().getInteger(R.integer.bubbles_max_rendered));
+ mBubbleContainer.setController(mStackAnimationController);
+ mBubbleContainer.setElevation(elevation);
+ mBubbleContainer.setPadding(padding, 0, padding, 0);
+ mBubbleContainer.setClipChildren(false);
+ addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
mExpandedViewContainer = (BubbleExpandedViewContainer)
LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view,
this /* parent */, false /* attachToRoot */);
@@ -128,11 +175,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
- mBubbleContainer = new FrameLayout(context);
- mBubbleContainer.setElevation(elevation);
- mBubbleContainer.setPadding(padding, 0, padding, 0);
- mBubbleContainer.setClipChildren(false);
- addView(mBubbleContainer);
+ mExpandedViewXAnim =
+ new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
+ mExpandedViewXAnim.setSpring(
+ new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+
+ mExpandedViewYAnim =
+ new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y);
+ mExpandedViewYAnim.setSpring(
+ new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
setClipChildren(false);
}
@@ -144,38 +199,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
}
@Override
- public void onMeasure(int widthSpec, int heightSpec) {
- super.onMeasure(widthSpec, heightSpec);
-
- int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
- MeasureSpec.UNSPECIFIED);
- if (mIsExpanded) {
- ViewGroup parent = (ViewGroup) getParent();
- int parentWidth = MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY);
- int parentHeight = MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY);
- measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec);
-
- int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
- MeasureSpec.UNSPECIFIED);
- measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight);
- setMeasuredDimension(widthSpec, parentHeight);
- } else {
- // Not expanded
- measureChild(mExpandedViewContainer, 0, 0);
-
- // Bubbles are translated a little to stack on top of each other
- widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY);
- measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec);
-
- heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(),
- MeasureSpec.EXACTLY);
- setMeasuredDimension(widthSpec, heightSpec);
- }
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float x = ev.getRawX();
float y = ev.getRawY();
@@ -293,9 +316,11 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
boolean updatePosition) {
bubbleView.update(entry);
if (updatePosition && !mIsExpanded) {
- // If alerting it gets promoted to top of the stack
- mBubbleContainer.removeView(bubbleView);
- mBubbleContainer.addView(bubbleView, 0);
+ // If alerting it gets promoted to top of the stack.
+ if (mBubbleContainer.indexOfChild(bubbleView) != 0) {
+ mBubbleContainer.removeViewAndThen(bubbleView,
+ () -> mBubbleContainer.addView(bubbleView, 0));
+ }
requestUpdate();
}
if (mIsExpanded && bubbleView.equals(mExpandedBubble)) {
@@ -359,36 +384,51 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
if (mIsExpanded != shouldExpand) {
mIsExpanded = shouldExpand;
updateExpandedBubble();
+ applyCurrentState();
+ //requestUpdate();
+
+ mIsAnimating = true;
+
+ Runnable updateAfter = () -> {
+ applyCurrentState();
+ mIsAnimating = false;
+ requestUpdate();
+ };
if (shouldExpand) {
- // Save current position so that we might return there
- savePosition();
+ mBubbleContainer.setController(mExpandedAnimationController);
+ mExpandedAnimationController.expandFromStack(
+ mStackAnimationController.getStackPosition(), updateAfter);
+ } else {
+ mBubbleContainer.cancelAllAnimations();
+ mExpandedAnimationController.collapseBackToStack(
+ () -> {
+ mBubbleContainer.setController(mStackAnimationController);
+ updateAfter.run();
+ });
}
- // Determine the translation for the stack
- Point position = shouldExpand
- ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize)
- : mCollapsedPosition;
- int delay = shouldExpand ? 0 : 100;
- AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position,
- 200, delay, null);
- if (!shouldExpand) {
- // First collapse the stack, then translate, maybe should expand at same time?
- animateStackExpansion(() -> translationAnim.start());
- } else {
- // First translate, then expand
- translationAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mIsAnimating = true;
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- animateStackExpansion(() -> mIsAnimating = false);
- }
- });
- translationAnim.start();
+ final float xStart =
+ mStackAnimationController.getStackPosition().x < getWidth() / 2
+ ? -mExpandedAnimateXDistance
+ : mExpandedAnimateXDistance;
+
+ final float yStart = Math.min(
+ mStackAnimationController.getStackPosition().y,
+ mExpandedAnimateYDistance);
+ final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding;
+
+ if (shouldExpand) {
+ mExpandedViewContainer.setTranslationX(xStart);
+ mExpandedViewContainer.setTranslationY(yStart);
+ mExpandedViewContainer.setAlpha(0f);
}
+
+ mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
+ mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
+ mExpandedViewContainer.animate()
+ .setDuration(100)
+ .alpha(shouldExpand ? 1f : 0f);
}
}
@@ -401,14 +441,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
+ mBubbleContainer.getPaddingStart();
}
- /**
- * Saves the current position of the stack, used to save user placement of the stack to
- * return to after an animation.
- */
- private void savePosition() {
- mCollapsedPosition = getPosition();
- }
-
private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) {
if (mExpandListener != null) {
NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null;
@@ -420,31 +452,154 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
return getBubbleAt(0);
}
- private BubbleView getBubbleAt(int i) {
+ /** Return the BubbleView at the given index from the bubble container. */
+ public BubbleView getBubbleAt(int i) {
return mBubbleContainer.getChildCount() > i
? (BubbleView) mBubbleContainer.getChildAt(i)
: null;
}
@Override
- public void setPosition(int x, int y) {
- setPositionX(x);
- setPositionY(y);
+ public void setPosition(float x, float y) {
+ mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
}
@Override
- public void setPositionX(int x) {
- setTranslationX(x);
+ public void setPositionX(float x) {
+ // Unsupported, use setPosition(x, y).
}
@Override
- public void setPositionY(int y) {
- setTranslationY(y);
+ public void setPositionY(float y) {
+ // Unsupported, use setPosition(x, y).
}
@Override
- public Point getPosition() {
- return new Point((int) getTranslationX(), (int) getTranslationY());
+ public PointF getPosition() {
+ return mStackAnimationController.getStackPosition();
+ }
+
+ /** Called when a drag operation on an individual bubble has started. */
+ public void onBubbleDragStart(BubbleView bubble) {
+ // TODO: Save position and snap back if not dismissed.
+ }
+
+ /** Called with the coordinates to which an individual bubble has been dragged. */
+ public void onBubbleDragged(BubbleView bubble, float x, float y) {
+ bubble.setTranslationX(x);
+ bubble.setTranslationY(y);
+ }
+
+ /** Called when a drag operation on an individual bubble has finished. */
+ public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) {
+ // TODO: Add fling to bottom to dismiss.
+ }
+
+ void onDragStart() {
+ if (mIsExpanded) {
+ return;
+ }
+
+ mStackAnimationController.cancelStackPositionAnimations();
+ mBubbleContainer.setController(mStackAnimationController);
+ mIsAnimating = false;
+ }
+
+ void onDragged(float x, float y) {
+ // TODO: We can drag if animating - just need to reroute inflight anims to drag point.
+ if (mIsExpanded) {
+ return;
+ }
+
+ mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
+ }
+
+ void onDragFinish(float x, float y, float velX, float velY) {
+ // TODO: Add fling to bottom to dismiss.
+
+ if (mIsExpanded || mIsAnimating) {
+ return;
+ }
+
+ final boolean stackOnLeftSide = x
+ - mBubbleContainer.getChildAt(0).getWidth() / 2
+ < mDisplaySize.x / 2;
+
+ final boolean stackShouldFlingLeft = stackOnLeftSide
+ ? velX < ESCAPE_VELOCITY
+ : velX < -ESCAPE_VELOCITY;
+
+ final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion();
+
+ // Target X translation (either the left or right side of the screen).
+ final float destinationRelativeX = stackShouldFlingLeft
+ ? stackBounds.left : stackBounds.right;
+
+ // Minimum velocity required for the stack to make it to the side of the screen.
+ final float escapeVelocity = getMinXVelocity(
+ x,
+ destinationRelativeX,
+ FLING_FRICTION_X);
+
+ // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so
+ // that it'll make it all the way to the side of the screen.
+ final float startXVelocity = stackShouldFlingLeft
+ ? Math.min(escapeVelocity, velX)
+ : Math.max(escapeVelocity, velX);
+
+ mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_X,
+ startXVelocity,
+ FLING_FRICTION_X,
+ new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SPRING_DAMPING_RATIO),
+ destinationRelativeX);
+
+ mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_Y,
+ velY,
+ FLING_FRICTION_Y,
+ new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SPRING_DAMPING_RATIO),
+ /* destination */ null);
+ }
+
+ /**
+ * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a
+ * given frictional force.
+ *
+ * This is not derived using real math, I just made it up because the math in FlingAnimation
+ * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it
+ * to the edge via Fling, it'll get Spring'd there anyway.
+ *
+ * TODO(tsuji, or someone who likes math): Figure out math.
+ */
+ private float getMinXVelocity(float x, float destX, float friction) {
+ return (destX - x) * (friction * 5) + ESCAPE_VELOCITY;
+ }
+
+ @Override
+ public void getBoundsOnScreen(Rect outRect) {
+ if (!mIsExpanded) {
+ mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
+ } else {
+ mBubbleContainer.getBoundsOnScreen(outRect);
+ }
+ }
+
+ private int getStatusBarHeight() {
+ if (getRootWindowInsets() != null) {
+ WindowInsets insets = getRootWindowInsets();
+ return Math.max(
+ insets.getSystemWindowInsetTop(),
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetTop()
+ : 0);
+ }
+
+ return 0;
}
private boolean isIntersecting(View view, float x, float y) {
@@ -478,22 +633,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
mExpandedViewContainer.setExpandedView(expandedView);
- expandedView.setCallback(new ActivityView.StateCallback() {
- @Override
- public void onActivityViewReady(ActivityView view) {
- Log.d(TAG, "onActivityViewReady("
- + mExpandedBubble.getEntry().key + "): " + view);
- view.startActivity(intent);
- }
-
- @Override
- public void onActivityViewDestroyed(ActivityView view) {
- NotificationEntry entry = mExpandedBubble != null
- ? mExpandedBubble.getEntry() : null;
- Log.d(TAG, "onActivityViewDestroyed(key="
- + ((entry != null) ? entry.key : "(none)") + "): " + view);
- }
- });
} else {
// Bubble with notification view expanded state
ExpandableNotificationRow row = mExpandedBubble.getRowView();
@@ -510,9 +649,8 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
mExpandedViewContainer.setHeaderText(null);
}
- int pointerPosition = mExpandedBubble.getPosition().x
- + (mExpandedBubble.getWidth() / 2);
- mExpandedViewContainer.setPointerPosition(pointerPosition);
+ float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2);
+ mExpandedViewContainer.setPointerPosition((int) pointerPosition);
}
private void applyCurrentState() {
@@ -522,7 +660,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
if (!mIsExpanded) {
mExpandedViewContainer.setExpandedView(null);
} else {
- mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
View expandedView = mExpandedViewContainer.getExpandedView();
if (expandedView instanceof ActivityView) {
if (expandedView.isAttachedToWindow()) {
@@ -537,53 +674,6 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
bv.updateDotVisibility();
bv.setZ(bubbsCount - i);
-
- int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
- ViewState viewState = new ViewState();
- viewState.initFrom(bv);
- viewState.xTranslation = transX;
- viewState.applyToView(bv);
-
- if (mIsExpanded) {
- // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
- bv.setTag(new Point(transX, 0));
- }
- }
- }
-
- private void animateStackExpansion(Runnable endRunnable) {
- int childCount = mBubbleContainer.getChildCount();
- for (int i = 0; i < childCount; i++) {
- BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i);
- int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i;
- int duration = childCount > 1 ? 200 : 0;
- if (mIsExpanded) {
- // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
- child.setTag(new Point(transX, 0));
- }
- ViewPropertyAnimator anim = child
- .animate()
- .setStartDelay(15 * i)
- .setDuration(duration)
- .setInterpolator(mIsExpanded
- ? new OvershootInterpolator()
- : new AccelerateInterpolator())
- .translationY(0)
- .translationX(transX);
- final int fi = i;
- // Probably want this choreographed with translation somehow / make it snappier
- anim.withStartAction(() -> mIsAnimating = true);
- anim.withEndAction(() -> {
- if (endRunnable != null) {
- endRunnable.run();
- }
- if (fi == mBubbleContainer.getChildCount() - 1) {
- applyCurrentState();
- mIsAnimating = false;
- requestUpdate();
- }
- });
- anim.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 97784b0f4f93..22cd2fcc3e72 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -19,7 +19,7 @@ package com.android.systemui.bubbles;
import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY;
import android.content.Context;
-import android.graphics.Point;
+import android.graphics.PointF;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -37,18 +37,16 @@ class BubbleTouchHandler implements View.OnTouchListener {
private BubbleController mController = Dependency.get(BubbleController.class);
private PipDismissViewController mDismissViewController;
- private BubbleMovementHelper mMovementHelper;
// The position of the bubble on down event
- private int mBubbleDownPosX;
- private int mBubbleDownPosY;
+ private float mBubbleDownPosX;
+ private float mBubbleDownPosY;
// The touch position on down event
- private int mDownX = -1;
- private int mDownY = -1;
+ private float mDownX = -1;
+ private float mDownY = -1;
private boolean mMovedEnough;
private int mTouchSlopSquared;
- private float mMinFlingVelocity;
private VelocityTracker mVelocityTracker;
private boolean mInDismissTarget;
@@ -71,32 +69,27 @@ class BubbleTouchHandler implements View.OnTouchListener {
/**
* Sets the position of the view.
*/
- void setPosition(int x, int y);
+ void setPosition(float x, float y);
/**
* Sets the x position of the view.
*/
- void setPositionX(int x);
+ void setPositionX(float x);
/**
* Sets the y position of the view.
*/
- void setPositionY(int y);
+ void setPositionY(float y);
/**
* @return the position of the view.
*/
- Point getPosition();
+ PointF getPosition();
}
public BubbleTouchHandler(Context context) {
final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mTouchSlopSquared = touchSlop * touchSlop;
-
- // Multiply by 3 for better fling
- mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3;
-
- mMovementHelper = new BubbleMovementHelper(context);
mDismissViewController = new PipDismissViewController(context);
}
@@ -119,9 +112,11 @@ class BubbleTouchHandler implements View.OnTouchListener {
FloatingView floatingView = (FloatingView) targetView;
boolean isBubbleStack = floatingView instanceof BubbleStackView;
- Point startPos = floatingView.getPosition();
- int rawX = (int) event.getRawX();
- int rawY = (int) event.getRawY();
+ PointF startPos = floatingView.getPosition();
+ float rawX = event.getRawX();
+ float rawY = event.getRawY();
+ float x = mBubbleDownPosX + rawX - mDownX;
+ float y = mBubbleDownPosY + rawY - mDownY;
switch (action) {
case MotionEvent.ACTION_DOWN:
trackMovement(event);
@@ -134,6 +129,13 @@ class BubbleTouchHandler implements View.OnTouchListener {
mDownX = rawX;
mDownY = rawY;
mMovedEnough = false;
+
+ if (isBubbleStack) {
+ stack.onDragStart();
+ } else {
+ stack.onBubbleDragStart((BubbleView) floatingView);
+ }
+
break;
case MotionEvent.ACTION_MOVE:
@@ -145,22 +147,23 @@ class BubbleTouchHandler implements View.OnTouchListener {
mDownX = rawX;
mDownY = rawY;
}
- final int deltaX = rawX - mDownX;
- final int deltaY = rawY - mDownY;
+ final float deltaX = rawX - mDownX;
+ final float deltaY = rawY - mDownY;
if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
mMovedEnough = true;
}
- int x = mBubbleDownPosX + rawX - mDownX;
- int y = mBubbleDownPosY + rawY - mDownY;
if (mMovedEnough) {
- if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) {
+ if (floatingView instanceof BubbleView) {
mBubbleDraggingOut = ((BubbleView) floatingView);
+ stack.onBubbleDragged(mBubbleDraggingOut, x, y);
+ } else {
+ stack.onDragged(x, y);
}
- floatingView.setPosition(x, y);
}
// TODO - when we're in the target stick to it / animate in some way?
- mInDismissTarget = mDismissViewController.updateTarget((View) floatingView);
+ mInDismissTarget = mDismissViewController.updateTarget(
+ isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView);
break;
case MotionEvent.ACTION_CANCEL:
@@ -181,19 +184,9 @@ class BubbleTouchHandler implements View.OnTouchListener {
final float velX = mVelocityTracker.getXVelocity();
final float velY = mVelocityTracker.getYVelocity();
if (isBubbleStack) {
- if ((Math.abs(velY) > mMinFlingVelocity)
- || (Math.abs(velX) > mMinFlingVelocity)) {
- // It's being flung somewhere
- mMovementHelper.animateFlingTo(stack, velX, velY).start();
- } else {
- // Magnet back to nearest edge
- mMovementHelper.animateMagnetTo(stack).start();
- }
+ stack.onDragFinish(x, y, velX, velY);
} else {
- // Individual bubble got dragged but not dismissed.. lets animate it back
- // into position
- Point toGoTo = (Point) ((View) floatingView).getTag();
- mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
+ stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY);
}
} else if (floatingView.equals(stack.getExpandedBubble())) {
stack.collapseStack();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index e8432b9a7017..dc948325e27a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -22,7 +22,7 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
@@ -60,6 +60,8 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati
private NotificationEntry mEntry;
private PendingIntent mAppOverlayIntent;
private ActivityView mActivityView;
+ private boolean mActivityViewReady;
+ private boolean mActivityViewStarted;
public BubbleView(Context context) {
this(context, null);
@@ -192,10 +194,10 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati
fraction = showDot ? fraction : 1 - fraction;
mBadgedImageView.setDotScale(fraction);
}).withEndAction(() -> {
- if (!showDot) {
- mBadgedImageView.setShowDot(false);
- }
- }).start();
+ if (!showDot) {
+ mBadgedImageView.setShowDot(false);
+ }
+ }).start();
}
}
@@ -232,8 +234,21 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati
*/
public ActivityView getActivityView() {
if (mActivityView == null) {
- mActivityView = new ActivityView(mContext);
+ mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
+ true /* singleTaskInstance */);
Log.d(TAG, "[getActivityView] created: " + mActivityView);
+ mActivityView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ mActivityViewReady = true;
+ mActivityView.startActivity(mAppOverlayIntent);
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ mActivityViewReady = false;
+ }
+ });
}
return mActivityView;
}
@@ -245,46 +260,44 @@ public class BubbleView extends FrameLayout implements BubbleTouchHandler.Floati
if (mActivityView == null) {
return;
}
- // HACK: Only release if initialized. There's no way to know if the ActivityView has
- // been initialized. Calling release() if it hasn't been initialized will crash.
-
+ if (!mActivityViewReady) {
+ // release not needed, never initialized?
+ mActivityView = null;
+ return;
+ }
+ // HACK: release() will crash if the view is not attached.
if (!mActivityView.isAttachedToWindow()) {
- // HACK: release() will crash if the view is not attached.
-
mActivityView.setVisibility(View.GONE);
tmpParent.addView(mActivityView, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
- try {
- mActivityView.release();
- } catch (IllegalStateException ex) {
- Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
- }
+
+ mActivityView.release();
((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
mActivityView = null;
}
@Override
- public void setPosition(int x, int y) {
+ public void setPosition(float x, float y) {
setPositionX(x);
setPositionY(y);
}
@Override
- public void setPositionX(int x) {
+ public void setPositionX(float x) {
setTranslationX(x);
}
@Override
- public void setPositionY(int y) {
+ public void setPositionY(float y) {
setTranslationY(y);
}
@Override
- public Point getPosition() {
- return new Point((int) getTranslationX(), (int) getTranslationY());
+ public PointF getPosition() {
+ return new PointF(getTranslationX(), getTranslationY());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
new file mode 100644
index 000000000000..4f870f6ceffc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -0,0 +1,159 @@
+/*
+ * 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.animation;
+
+import android.graphics.PointF;
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import com.google.android.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Animation controller for bubbles when they're in their expanded state, or animating to/from the
+ * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be
+ * dismissed.
+ */
+public class ExpandedAnimationController
+ extends PhysicsAnimationLayout.PhysicsAnimationController {
+
+ /**
+ * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack}
+ * and used to return to stack form in {@link #collapseBackToStack}.
+ */
+ private PointF mExpandedFrom;
+
+ /** Horizontal offset between bubbles, which we need to know to re-stack them. */
+ private float mStackOffsetPx;
+ /** Spacing between bubbles in the expanded state. */
+ private float mBubblePaddingPx;
+ /** Size of each bubble. */
+ private float mBubbleSizePx;
+
+ @Override
+ protected void setLayout(PhysicsAnimationLayout layout) {
+ super.setLayout(layout);
+ mStackOffsetPx = layout.getResources().getDimensionPixelSize(
+ R.dimen.bubble_stack_offset);
+ mBubblePaddingPx = layout.getResources().getDimensionPixelSize(
+ R.dimen.bubble_padding);
+ mBubbleSizePx = layout.getResources().getDimensionPixelSize(
+ R.dimen.individual_bubble_size);
+ }
+
+ /**
+ * Animates expanding the bubbles into a row along the top of the screen.
+ *
+ * @return The y-value to which the bubbles were expanded, in case that's useful.
+ */
+ public float expandFromStack(PointF expandedFrom, Runnable after) {
+ mExpandedFrom = expandedFrom;
+
+ // How much to translate the next bubble, so that it is not overlapping the previous one.
+ float translateNextBubbleXBy = mBubblePaddingPx;
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
+ mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY());
+ translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
+ }
+
+ runAfterTranslationsEnd(after);
+ return getExpandedY();
+ }
+
+ /** Animate collapsing the bubbles back to their stacked position. */
+ public void collapseBackToStack(Runnable after) {
+ // Stack to the left if we're going to the left, or right if not.
+ final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1;
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
+ mLayout.animatePositionForChildAtIndex(
+ i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y);
+ }
+
+ runAfterTranslationsEnd(after);
+ }
+
+ /** The Y value of the row of expanded bubbles. */
+ private float getExpandedY() {
+ final WindowInsets insets = mLayout.getRootWindowInsets();
+ if (insets != null) {
+ return mBubblePaddingPx + Math.max(
+ insets.getSystemWindowInsetTop(),
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetTop()
+ : 0);
+ }
+
+ return mBubblePaddingPx;
+ }
+
+ /** Runs the given Runnable after all translation-related animations have ended. */
+ private void runAfterTranslationsEnd(Runnable after) {
+ DynamicAnimation.OnAnimationEndListener allEndedListener =
+ (animation, canceled, value, velocity) -> {
+ if (!mLayout.arePropertiesAnimating(
+ DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y)) {
+ after.run();
+ }
+ };
+
+ mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X);
+ mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y);
+ }
+
+ @Override
+ Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+ return Sets.newHashSet(
+ DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y);
+ }
+
+ @Override
+ int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+ return NONE;
+ }
+
+ @Override
+ float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+ return 0;
+ }
+
+ @Override
+ SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+ return new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ }
+
+ @Override
+ void onChildAdded(View child, int index) {
+ // TODO: Animate the new bubble into the row, and push the other bubbles out of the way.
+ child.setTranslationY(getExpandedY());
+ }
+
+ @Override
+ void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+ // TODO: Animate the bubble out, and pull the other bubbles into its position.
+ actuallyRemove.run();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
new file mode 100644
index 000000000000..4e0abc8009b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.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.systemui.bubbles.animation;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+/**
+ * End listener that removes itself from its animation when called for the first time. Useful since
+ * anonymous OnAnimationEndListener instances can't pass themselves to
+ * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass
+ * implementation.
+ */
+public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener {
+
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ animation.removeEndListener(this);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
new file mode 100644
index 000000000000..1ced3a4daac6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -0,0 +1,496 @@
+/*
+ * 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.animation;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Layout that constructs physics-based animations for each of its children, which behave according
+ * to settings provided by a {@link PhysicsAnimationController} instance.
+ *
+ * See physics-animation-layout.md.
+ */
+public class PhysicsAnimationLayout extends FrameLayout {
+ private static final String TAG = "Bubbs.PAL";
+
+ /**
+ * Controls the construction, configuration, and use of the physics animations supplied by this
+ * layout.
+ */
+ abstract static class PhysicsAnimationController {
+
+ /**
+ * Constant to return from {@link #getNextAnimationInChain} if the animation should not be
+ * chained at all.
+ */
+ protected static final int NONE = -1;
+
+ /** Set of properties for which the layout should construct physics animations. */
+ abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties();
+
+ /**
+ * Returns the index of the next animation after the given index in the animation chain, or
+ * {@link #NONE} if it should not be chained, or if the chain should end at the given index.
+ *
+ * If a next index is returned, an update listener will be added to the animation at the
+ * given index that dispatches value updates to the animation at the next index. This
+ * creates a 'following' effect.
+ *
+ * Typical implementations of this method will return either index + 1, or index - 1, to
+ * create forward or backward chains between adjacent child views, but this is not required.
+ */
+ abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index);
+
+ /**
+ * Offsets to be added to the value that chained animations of the given property dispatch
+ * to subsequent child animations.
+ *
+ * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles
+ * stack off to the left or right side slightly.
+ */
+ abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property);
+
+ /**
+ * Returns the SpringForce to be used for the given child view's property animation. Despite
+ * these usually being similar or identical across properties and views, {@link SpringForce}
+ * also contains the SpringAnimation's final position, so we have to construct a new one for
+ * each animation rather than using a constant.
+ */
+ abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view);
+
+ /**
+ * Called when a new child is added at the specified index. Controllers can use this
+ * opportunity to animate in the new view.
+ */
+ abstract void onChildAdded(View child, int index);
+
+ /**
+ * Called when a child is to be removed from the layout. Controllers can use this
+ * opportunity to animate out the new view before calling the provided callback to actually
+ * remove it.
+ *
+ * Controllers should be careful to ensure that actuallyRemove is called on all code paths
+ * or child views will never be removed.
+ */
+ abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove);
+
+ protected PhysicsAnimationLayout mLayout;
+
+ PhysicsAnimationController() { }
+
+ protected void setLayout(PhysicsAnimationLayout layout) {
+ this.mLayout = layout;
+ }
+
+ protected PhysicsAnimationLayout getLayout() {
+ return mLayout;
+ }
+ }
+
+ /**
+ * End listeners that are called when every child's animation of the given property has
+ * finished.
+ */
+ protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener>
+ mEndListenerForProperty = new HashMap<>();
+
+ /**
+ * List of views that were passed to removeView, but are currently being animated out. These
+ * views will be actually removed by the controller (via super.removeView) once they're done
+ * animating out.
+ */
+ private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>();
+
+ /** The currently active animation controller. */
+ private PhysicsAnimationController mController;
+
+ /**
+ * The maximum number of children to render and animate at a time. See
+ * {@link #setMaxRenderedChildren}.
+ */
+ private int mMaxRenderedChildren = 5;
+
+ public PhysicsAnimationLayout(Context context) {
+ super(context);
+ }
+
+ /**
+ * The maximum number of children to render and animate at a time. Any child views added beyond
+ * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view,
+ * the corresponding property will be set with no animation.
+ */
+ public void setMaxRenderedChildren(int max) {
+ this.mMaxRenderedChildren = max;
+ }
+
+ /**
+ * Sets the animation controller and constructs or reconfigures the layout's physics animations
+ * to meet the controller's specifications.
+ */
+ public void setController(PhysicsAnimationController controller) {
+ cancelAllAnimations();
+ mEndListenerForProperty.clear();
+
+ this.mController = controller;
+ mController.setLayout(this);
+
+ // Set up animations for this controller's animated properties.
+ for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+ setUpAnimationsForProperty(property);
+ }
+ }
+
+ /**
+ * Sets an end listener that will be called when all child animations for a given property have
+ * stopped running.
+ */
+ public void setEndListenerForProperty(
+ DynamicAnimation.OnAnimationEndListener listener,
+ DynamicAnimation.ViewProperty property) {
+ mEndListenerForProperty.put(property, listener);
+ }
+
+ /**
+ * Removes the end listener that would have been called when all child animations for a given
+ * property stopped running.
+ */
+ public void removeEndListenerForProperty(DynamicAnimation.ViewProperty property) {
+ mEndListenerForProperty.remove(property);
+ }
+
+ /**
+ * Returns the index of the view that precedes the given index, ignoring views that were passed
+ * to removeView, but are currently being animated out before actually being removed.
+ *
+ * @return index of the preceding view, or -1 if there are none.
+ */
+ public int getPrecedingNonRemovedViewIndex(int index) {
+ for (int i = index + 1; i < getChildCount(); i++) {
+ View precedingView = getChildAt(i);
+ if (!mViewsToBeActuallyRemoved.contains(precedingView)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+ setChildrenVisibility();
+
+ // Set up animations for the new view, if the controller is set. If it isn't set, we'll be
+ // setting up animations for all children when setController is called.
+ if (mController != null) {
+ for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+ setUpAnimationForChild(property, child, index);
+ }
+
+ mController.onChildAdded(child, index);
+ }
+ }
+
+ @Override
+ public void removeView(View view) {
+ removeViewAndThen(view, /* callback */ null);
+ }
+
+ /**
+ * Let the controller know that this view should be removed, and then call the callback once the
+ * controller has finished any removal animations and the view has actually been removed.
+ */
+ public void removeViewAndThen(View view, Runnable callback) {
+ if (mController != null) {
+ final int index = indexOfChild(view);
+ // Remove the view only if it exists in this layout, and we're not already working on
+ // animating its removal.
+ if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) {
+ mViewsToBeActuallyRemoved.add(view);
+ setChildrenVisibility();
+
+ // Tell the controller to animate this view out, and call the callback when it wants
+ // to actually remove the view.
+ mController.onChildToBeRemoved(view, index, () -> {
+ removeViewImmediateAndThen(view, callback);
+ mViewsToBeActuallyRemoved.remove(view);
+ });
+ }
+ } else {
+ // Without a controller, nobody will animate this view out, so it gets an unceremonious
+ // departure.
+ removeViewImmediateAndThen(view, callback);
+ }
+ }
+
+ /** Checks whether any animations of the given properties are still running. */
+ public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) {
+ for (int i = 0; i < getChildCount(); i++) {
+ for (DynamicAnimation.ViewProperty property : properties) {
+ if (getAnimationAtIndex(property, i).isRunning()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /** Cancels all animations that are running on all child views, for all properties. */
+ public void cancelAllAnimations() {
+ if (mController == null) {
+ return;
+ }
+
+ for (int i = 0; i < getChildCount(); i++) {
+ for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+ getAnimationAtIndex(property, i).cancel();
+ }
+ }
+ }
+
+ /**
+ * Animates the property of the child at the given index to the given value, then runs the
+ * callback provided when the animation ends.
+ */
+ protected void animateValueForChildAtIndex(
+ DynamicAnimation.ViewProperty property,
+ int index,
+ float value,
+ float startVel,
+ Runnable after) {
+ if (index < getChildCount()) {
+ final SpringAnimation animation = getAnimationAtIndex(property, index);
+ if (after != null) {
+ animation.addEndListener(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+ float value, float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ after.run();
+ }
+ });
+ }
+
+ if (startVel != Float.MAX_VALUE) {
+ animation.setStartVelocity(startVel);
+ }
+
+ animation.animateToFinalPosition(value);
+ }
+ }
+
+ /** Shortcut to animate a value with a callback, but no start velocity. */
+ protected void animateValueForChildAtIndex(
+ DynamicAnimation.ViewProperty property,
+ int index,
+ float value,
+ Runnable after) {
+ animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after);
+ }
+
+ /** Shortcut to animate a value with a start velocity, but no callback. */
+ protected void animateValueForChildAtIndex(
+ DynamicAnimation.ViewProperty property,
+ int index,
+ float value,
+ float startVel) {
+ animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null);
+ }
+
+ /** Shortcut to animate a value without changing the velocity or providing a callback. */
+ protected void animateValueForChildAtIndex(
+ DynamicAnimation.ViewProperty property,
+ int index,
+ float value) {
+ animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null);
+ }
+
+ /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */
+ protected void animatePositionForChildAtIndex(int index, float x, float y) {
+ animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x);
+ animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y);
+ }
+
+ /** Whether the first child would be left of center if translated to the given x value. */
+ protected boolean isFirstChildXLeftOfCenter(float x) {
+ if (getChildCount() > 0) {
+ return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2;
+ } else {
+ return false; // If there's no first child, really anything is correct, right?
+ }
+ }
+
+ /** ViewProperty's toString is useless, this returns a readable name for debug logging. */
+ protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) {
+ if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ return "TRANSLATION_X";
+ } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+ return "TRANSLATION_Y";
+ } else if (property.equals(DynamicAnimation.SCALE_X)) {
+ return "SCALE_X";
+ } else if (property.equals(DynamicAnimation.SCALE_Y)) {
+ return "SCALE_Y";
+ } else if (property.equals(DynamicAnimation.ALPHA)) {
+ return "ALPHA";
+ } else {
+ return "Unknown animation property.";
+ }
+ }
+
+
+ /** Immediately removes the view, without notifying the controller, then runs the callback. */
+ private void removeViewImmediateAndThen(View view, Runnable callback) {
+ super.removeView(view);
+
+ if (callback != null) {
+ callback.run();
+ }
+
+ setChildrenVisibility();
+ }
+
+ /**
+ * Retrieves the animation of the given property from the view at the given index via the view
+ * tag system.
+ */
+ private SpringAnimation getAnimationAtIndex(
+ DynamicAnimation.ViewProperty property, int index) {
+ return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property));
+ }
+
+ /** Sets up SpringAnimations of the given property for each child view in the layout. */
+ private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
+ for (int i = 0; i < getChildCount(); i++) {
+ setUpAnimationForChild(property, getChildAt(i), i);
+ }
+ }
+
+ /** Constructs a SpringAnimation of the given property for a child view. */
+ private void setUpAnimationForChild(
+ DynamicAnimation.ViewProperty property, View child, int index) {
+ SpringAnimation newAnim = new SpringAnimation(child, property);
+ newAnim.addUpdateListener((animation, value, velocity) -> {
+ final int nextAnimInChain =
+ mController.getNextAnimationInChain(property, indexOfChild(child));
+ if (nextAnimInChain == PhysicsAnimationController.NONE) {
+ return;
+ }
+
+ final int animIndex = indexOfChild(child);
+ final float offset =
+ mController.getOffsetForChainedPropertyAnimation(property);
+
+ // If this property's animations should be chained, then check to see if there is a
+ // subsequent animation within the rendering limit, and if so, tell it to animate to
+ // this animation's new value (plus the offset).
+ if (nextAnimInChain < Math.min(
+ getChildCount(),
+ mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) {
+ getAnimationAtIndex(property, animIndex + 1)
+ .animateToFinalPosition(value + offset);
+ } else if (nextAnimInChain < getChildCount()) {
+ // If the next child view is not rendered, update the property directly without
+ // animating it, so that the view is still in the correct state if it later
+ // becomes visible.
+ for (int i = nextAnimInChain; i < getChildCount(); i++) {
+ // 'value' here is the value of the last child within the rendering limit,
+ // not the first child's value - so we want to subtract the last child's
+ // index when calculating the offset.
+ property.setValue(getChildAt(i), value + offset * (i - animIndex));
+ }
+ }
+ });
+
+ newAnim.setSpring(mController.getSpringForce(property, child));
+ newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property));
+ child.setTag(getTagIdForProperty(property), newAnim);
+ }
+
+ /** Hides children beyond the max rendering count. */
+ private void setChildrenVisibility() {
+ for (int i = 0; i < getChildCount(); i++) {
+ getChildAt(i).setVisibility(
+ // Ignore views that are animating out when calculating whether to hide the
+ // view. That is, if we're supposed to render 5 views, but 4 are animating out
+ // and will soon be removed, render up to 9 views temporarily.
+ i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())
+ ? View.VISIBLE
+ : View.GONE);
+ }
+ }
+
+ /** Return a stable ID to use as a tag key for the given property's animations. */
+ private int getTagIdForProperty(DynamicAnimation.ViewProperty property) {
+ if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ return R.id.translation_x_dynamicanimation_tag;
+ } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+ return R.id.translation_y_dynamicanimation_tag;
+ } else if (property.equals(DynamicAnimation.SCALE_X)) {
+ return R.id.scale_x_dynamicanimation_tag;
+ } else if (property.equals(DynamicAnimation.SCALE_Y)) {
+ return R.id.scale_y_dynamicanimation_tag;
+ } else if (property.equals(DynamicAnimation.ALPHA)) {
+ return R.id.alpha_dynamicanimation_tag;
+ }
+
+ return -1;
+ }
+
+ /**
+ * End listener that is added to each individual DynamicAnimation, which dispatches to a single
+ * listener when every other animation of the given property is no longer running.
+ *
+ * This is required since chained DynamicAnimations can stop and start again due to changes in
+ * upstream animations. This means that adding an end listener to just the last animation is not
+ * sufficient. By firing only when every other animation on the property has stopped running, we
+ * ensure that no animation will be restarted after the single end listener is called.
+ */
+ protected class AllAnimationsForPropertyFinishedEndListener
+ implements DynamicAnimation.OnAnimationEndListener {
+ private DynamicAnimation.ViewProperty mProperty;
+
+ AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) {
+ this.mProperty = property;
+ }
+
+ @Override
+ public void onAnimationEnd(
+ DynamicAnimation anim, boolean canceled, float value, float velocity) {
+ if (!arePropertiesAnimating(mProperty)) {
+ if (mEndListenerForProperty.containsKey(mProperty)) {
+ mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value,
+ velocity);
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
new file mode 100644
index 000000000000..0f5137618258
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -0,0 +1,455 @@
+/*
+ * 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.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+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.R;
+
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop
+ * each other with a slight offset to the left or right (depending on which side of the screen they
+ * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of
+ * the screen.
+ */
+public class StackAnimationController extends
+ PhysicsAnimationLayout.PhysicsAnimationController {
+
+ private static final String TAG = "Bubbs.StackCtrl";
+
+ /** Scale factor to use initially for new bubbles being animated in. */
+ private static final float ANIMATE_IN_STARTING_SCALE = 1.15f;
+
+ /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */
+ private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4;
+
+ /**
+ * Values to use for the default {@link SpringForce} provided to the physics animation layout.
+ */
+ private static final float DEFAULT_STIFFNESS = 2500f;
+ private static final float DEFAULT_BOUNCINESS = 0.85f;
+
+ /**
+ * The canonical position of the stack. This is typically the position of the first bubble, but
+ * we need to keep track of it separately from the first bubble's translation in case there are
+ * no bubbles, or the first bubble was just added and being animated to its new position.
+ */
+ private PointF mStackPosition = new PointF();
+
+ /**
+ * Animations on the stack position itself, which would have been started in
+ * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to
+ * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect)
+ * to a legal position on the side of the screen.
+ */
+ private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations =
+ new HashMap<>();
+
+ /** Horizontal offset of bubbles in the stack. */
+ private float mStackOffset;
+ /** Diameter of the bubbles themselves. */
+ private int mIndividualBubbleSize;
+ /** Size of spacing around the bubbles, separating it from the edge of the screen. */
+ private int mBubblePadding;
+ /** How far offscreen the stack rests. */
+ private int mBubbleOffscreen;
+ /** How far down the screen the stack starts, when there is no pre-existing location. */
+ private int mStackStartingVerticalOffset;
+
+ private Point mDisplaySize;
+ private RectF mAllowableStackPositionRegion;
+
+ @Override
+ protected void setLayout(PhysicsAnimationLayout layout) {
+ super.setLayout(layout);
+
+ Resources res = layout.getResources();
+ mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+ mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
+ mStackStartingVerticalOffset =
+ res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
+
+ mDisplaySize = new Point();
+ WindowManager wm =
+ (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(mDisplaySize);
+ }
+
+ /**
+ * Instantly move the first bubble to the given point, and animate the rest of the stack behind
+ * it with the 'following' effect.
+ */
+ public void moveFirstBubbleWithStackFollowing(float x, float y) {
+ moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x);
+ moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y);
+ }
+
+ /**
+ * The position of the stack - typically the position of the first bubble; if no bubbles have
+ * been added yet, it will be where the first bubble will go when added.
+ */
+ public PointF getStackPosition() {
+ return mStackPosition;
+ }
+
+ /**
+ * Flings the first bubble along the given property's axis, using the provided configuration
+ * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
+ * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final
+ * position.
+ */
+ public void flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.ViewProperty property,
+ float vel,
+ float friction,
+ SpringForce spring,
+ Float finalPosition) {
+ Log.d(TAG, String.format("Flinging %s.",
+ PhysicsAnimationLayout.getReadablePropertyName(property)));
+
+ StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+ final float currentValue = firstBubbleProperty.getValue(this);
+ final RectF bounds = getAllowableStackPositionRegion();
+ final float min =
+ property.equals(DynamicAnimation.TRANSLATION_X)
+ ? bounds.left
+ : bounds.top;
+ final float max =
+ property.equals(DynamicAnimation.TRANSLATION_X)
+ ? bounds.right
+ : bounds.bottom;
+
+ FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty);
+ flingAnimation.setFriction(friction)
+ .setStartVelocity(vel)
+
+ // If the bubble's property value starts beyond the desired min/max, use that value
+ // instead so that the animation won't immediately end. If, for example, the user
+ // drags the bubbles into the navigation bar, but then flings them upward, we want
+ // the fling to occur despite temporarily having a value outside of the min/max. If
+ // the bubbles are out of bounds and flung even farther out of bounds, the fling
+ // animation will halt immediately and the SpringAnimation will take over, springing
+ // it in reverse to the (legal) final position.
+ .setMinValue(Math.min(currentValue, min))
+ .setMaxValue(Math.max(currentValue, max))
+
+ .addEndListener((animation, canceled, endValue, endVelocity) -> {
+ if (!canceled) {
+ springFirstBubbleWithStackFollowing(property, spring, endVelocity,
+ finalPosition != null
+ ? finalPosition
+ : Math.max(min, Math.min(max, endValue)));
+ }
+ });
+
+ cancelStackPositionAnimation(property);
+ mStackPositionAnimations.put(property, flingAnimation);
+ flingAnimation.start();
+ }
+
+ /**
+ * Cancel any stack position animations that were started by calling
+ * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end
+ * listeners.
+ */
+ public void cancelStackPositionAnimations() {
+ cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
+ cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
+
+ mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
+ mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y);
+ }
+
+ /**
+ * Returns the region within which the stack is allowed to rest. This goes slightly off the left
+ * and right sides of the screen, below the status bar/cutout and above the navigation bar.
+ * While the stack is not allowed to rest outside of these bounds, it can temporarily be
+ * animated or dragged beyond them.
+ */
+ public RectF getAllowableStackPositionRegion() {
+ final WindowInsets insets = mLayout.getRootWindowInsets();
+ mAllowableStackPositionRegion = new RectF();
+
+ if (insets != null) {
+ mAllowableStackPositionRegion.left =
+ -mBubbleOffscreen
+ - mBubblePadding
+ + Math.max(
+ insets.getSystemWindowInsetLeft(),
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetLeft()
+ : 0);
+ mAllowableStackPositionRegion.right =
+ mLayout.getWidth()
+ - mIndividualBubbleSize
+ + mBubbleOffscreen
+ - mBubblePadding
+ - Math.max(
+ insets.getSystemWindowInsetRight(),
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetRight()
+ : 0);
+
+ mAllowableStackPositionRegion.top =
+ mBubblePadding
+ + Math.max(
+ insets.getSystemWindowInsetTop(),
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetTop()
+ : 0);
+ mAllowableStackPositionRegion.bottom =
+ mLayout.getHeight()
+ - mIndividualBubbleSize
+ - mBubblePadding
+ - Math.max(
+ insets.getSystemWindowInsetBottom(),
+ insets.getDisplayCutout() != null
+ ? insets.getDisplayCutout().getSafeInsetBottom()
+ : 0);
+ }
+
+ return mAllowableStackPositionRegion;
+ }
+
+ @Override
+ Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+ return Sets.newHashSet(
+ DynamicAnimation.TRANSLATION_X, // For positioning.
+ DynamicAnimation.TRANSLATION_Y,
+ DynamicAnimation.ALPHA, // For fading in new bubbles.
+ DynamicAnimation.SCALE_X, // For 'popping in' new bubbles.
+ DynamicAnimation.SCALE_Y);
+ }
+
+ @Override
+ int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+ if (property.equals(DynamicAnimation.TRANSLATION_X)
+ || property.equals(DynamicAnimation.TRANSLATION_Y)) {
+ return index + 1; // Just chain them linearly.
+ } else {
+ return NONE;
+ }
+ }
+
+
+ @Override
+ float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+ if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ // Offset to the left if we're on the left, or the right otherwise.
+ return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
+ ? -mStackOffset : mStackOffset;
+ } else {
+ return 0f;
+ }
+ }
+
+ @Override
+ SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+ return new SpringForce()
+ .setDampingRatio(DEFAULT_BOUNCINESS)
+ .setStiffness(DEFAULT_STIFFNESS);
+ }
+
+ @Override
+ void onChildAdded(View child, int index) {
+ // If this is the first child added, position the stack in its starting position.
+ if (mLayout.getChildCount() == 1) {
+ moveStackToStartPosition();
+ }
+
+ if (mLayout.indexOfChild(child) == 0) {
+ child.setTranslationY(mStackPosition.y);
+
+ // Pop in the new bubble.
+ child.setScaleX(ANIMATE_IN_STARTING_SCALE);
+ child.setScaleY(ANIMATE_IN_STARTING_SCALE);
+ mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f);
+ mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f);
+
+ // Fade in the new bubble.
+ child.setAlpha(0);
+ mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f);
+
+ // Start the new bubble 4x the normal offset distance in the opposite direction. We'll
+ // animate in from this position. Since the animations are chained, when the new bubble
+ // flies in from the side, it will push the other ones out of the way.
+ float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+ child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset));
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x);
+ }
+ }
+
+ @Override
+ void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+ // Animate the child out, actually removing it once its alpha is zero.
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.ALPHA, index, 0f, () -> {
+ actuallyRemove.run();
+ });
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE);
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE);
+
+ final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount();
+ if (hasPrecedingChild) {
+ final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index);
+ if (precedingViewIndex >= 0) {
+ final float offsetX =
+ getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+ mLayout.animatePositionForChildAtIndex(
+ precedingViewIndex,
+ mStackPosition.x + (index * offsetX),
+ mStackPosition.y);
+ }
+ }
+ }
+
+ /** Moves the stack, without any animation, to the starting position. */
+ private void moveStackToStartPosition() {
+ mLayout.post(() -> setStackPosition(
+ getAllowableStackPositionRegion().right,
+ getAllowableStackPositionRegion().top + mStackStartingVerticalOffset));
+ }
+
+ /**
+ * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent
+ * bubbles to animate 'following' to the new location.
+ */
+ private void moveFirstBubbleWithStackFollowing(
+ DynamicAnimation.ViewProperty property, float value) {
+
+ // Update the canonical stack position.
+ if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ mStackPosition.x = value;
+ } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+ mStackPosition.y = value;
+ }
+
+ if (mLayout.getChildCount() > 0) {
+ property.setValue(mLayout.getChildAt(0), value);
+ mLayout.animateValueForChildAtIndex(
+ property,
+ /* index */ 1,
+ value + getOffsetForChainedPropertyAnimation(property));
+ }
+ }
+
+ /** Moves the stack to a position instantly, with no animation. */
+ private void setStackPosition(float x, float y) {
+ Log.d(TAG, String.format("Setting position to (%f, %f).", x, y));
+ mStackPosition.set(x, y);
+
+ cancelStackPositionAnimations();
+
+ // Since we're not using the chained animations, apply the offsets manually.
+ final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+ final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y);
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
+ mLayout.getChildAt(i).setTranslationX(x + (i * xOffset));
+ mLayout.getChildAt(i).setTranslationY(y + (i * yOffset));
+ }
+ }
+
+ /**
+ * Springs the first bubble to the given final position, with the rest of the stack 'following'.
+ */
+ private void springFirstBubbleWithStackFollowing(
+ DynamicAnimation.ViewProperty property, SpringForce spring,
+ float vel, float finalPosition) {
+
+ Log.d(TAG, String.format("Springing %s to final position %f.",
+ PhysicsAnimationLayout.getReadablePropertyName(property),
+ finalPosition));
+
+ StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+ SpringAnimation springAnimation =
+ new SpringAnimation(this, firstBubbleProperty)
+ .setSpring(spring)
+ .setStartVelocity(vel);
+
+ cancelStackPositionAnimation(property);
+ mStackPositionAnimations.put(property, springAnimation);
+ springAnimation.animateToFinalPosition(finalPosition);
+ }
+
+ /**
+ * Cancels any outstanding first bubble property animations that are running. This does not
+ * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only
+ * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and
+ * {@link #flingThenSpringFirstBubbleWithStackFollowing}.
+ */
+ private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) {
+ if (mStackPositionAnimations.containsKey(property)) {
+ mStackPositionAnimations.get(property).cancel();
+ }
+ }
+
+ /**
+ * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
+ * translation and animate the rest of the stack with it. A DynamicAnimation can animate this
+ * property directly to move the first bubble and cause the stack to 'follow' to the new
+ * location.
+ *
+ * This could also be achieved by simply animating the first bubble view and adding an update
+ * listener to dispatch movement to the rest of the stack. However, this would require
+ * duplication of logic in that update handler - it's simpler to keep all logic contained in the
+ * {@link #moveFirstBubbleWithStackFollowing} method.
+ */
+ private class StackPositionProperty
+ extends FloatPropertyCompat<StackAnimationController> {
+ private final DynamicAnimation.ViewProperty mProperty;
+
+ private StackPositionProperty(DynamicAnimation.ViewProperty property) {
+ super(property.toString());
+ mProperty = property;
+ }
+
+ @Override
+ public float getValue(StackAnimationController controller) {
+ return mProperty.getValue(mLayout.getChildAt(0));
+ }
+
+ @Override
+ public void setValue(StackAnimationController controller, float value) {
+ moveFirstBubbleWithStackFollowing(mProperty, value);
+ }
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 7b18fad0e105..f5ac0d39d61f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1090,9 +1090,16 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
+ protected int getActionLayoutId(Context context) {
+ if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) {
+ return com.android.systemui.R.layout.global_actions_grid_item;
+ }
+ return com.android.systemui.R.layout.global_actions_item;
+ }
+
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
- View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent,
+ View v = inflater.inflate(getActionLayoutId(context), parent,
false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
@@ -1498,7 +1505,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
window.setBackgroundDrawable(mGradientDrawable);
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- setContentView(com.android.systemui.R.layout.global_actions_wrapped);
+
+ setContentView(getGlobalActionsLayoutId(context));
mGlobalActionsLayout = (MultiListLayout)
findViewById(com.android.systemui.R.id.global_actions_view);
mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
@@ -1515,6 +1523,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
setTitle(R.string.global_actions);
}
+ private int getGlobalActionsLayoutId(Context context) {
+ if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) {
+ return com.android.systemui.R.layout.global_actions_grid;
+ }
+ return com.android.systemui.R.layout.global_actions_wrapped;
+ }
+
private void updateList() {
mGlobalActionsLayout.removeAllItems();
ArrayList<Action> separatedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
new file mode 100644
index 000000000000..0e49b5f3cd2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -0,0 +1,101 @@
+/*
+ * 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.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.HardwareBgDrawable;
+import com.android.systemui.MultiListLayout;
+
+/**
+ * Grid-based implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsGridLayout extends MultiListLayout {
+
+ boolean mBackgroundsSet;
+
+ public GlobalActionsGridLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void setBackgrounds() {
+ HardwareBgDrawable listBackground = new HardwareBgDrawable(true, true, getContext());
+ HardwareBgDrawable separatedViewBackground = new HardwareBgDrawable(true, true,
+ getContext());
+ getListView().setBackground(listBackground);
+ getSeparatedView().setBackground(separatedViewBackground);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // backgrounds set only once, the first time onMeasure is called after inflation
+ if (getListView() != null && !mBackgroundsSet) {
+ setBackgrounds();
+ mBackgroundsSet = true;
+ }
+ }
+
+ @Override
+ public void setExpectedListItemCount(int count) {
+ mExpectedListItemCount = count;
+ getListView().setExpectedCount(count);
+ }
+
+ @Override
+ protected ViewGroup getSeparatedView() {
+ return findViewById(com.android.systemui.R.id.separated_button);
+ }
+
+ @Override
+ protected ListGridLayout getListView() {
+ return findViewById(android.R.id.list);
+ }
+
+ @Override
+ public void removeAllItems() {
+ ViewGroup separatedList = getSeparatedView();
+ ListGridLayout list = getListView();
+ if (separatedList != null) {
+ separatedList.removeAllViews();
+ }
+ if (list != null) {
+ list.removeAllItems();
+ }
+ }
+
+ @Override
+ public ViewGroup getParentView(boolean separated, int index) {
+ if (separated) {
+ return getSeparatedView();
+ } else {
+ return getListView().getParentView(index);
+ }
+ }
+
+ /**
+ * Not used in this implementation of the Global Actions Menu, but necessary for some others.
+ */
+ @Override
+ public void setDivisionView(View v) {
+
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
new file mode 100644
index 000000000000..37755155751f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -0,0 +1,111 @@
+/*
+ * 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.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Layout which uses nested LinearLayouts to create a grid with the following behavior:
+ *
+ * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item
+ * count.
+ * * Display and hide sub-lists as needed, depending on the expected item count.
+ * * Favor bias toward having more rows or columns depending on the orientation of the device
+ * (TODO(123344999): Implement this, currently always favors adding more rows.)
+ * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows
+ * or columns depending on the orientation of the device.
+ * (TODO(123344999): Implement this, currently always columns.)
+ *
+ * While we could implement this behavior with a GridLayout, it would take significantly more
+ * time and effort, and would require more substantial refactoring of the existing code in
+ * GlobalActionsDialog, since it would require manipulation of the child items themselves.
+ *
+ */
+
+public class ListGridLayout extends LinearLayout {
+ private int mExpectedCount;
+ private int mRows;
+ private int mColumns;
+
+ public ListGridLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Remove all items from this grid.
+ */
+ public void removeAllItems() {
+ for (int i = 0; i < getChildCount(); i++) {
+ ViewGroup subList = (ViewGroup) getChildAt(i);
+ if (subList != null) {
+ subList.removeAllViews();
+ }
+ }
+ }
+
+ /**
+ * Get the parent view associated with the item which should be placed at the given position.
+ */
+ public ViewGroup getParentView(int index) {
+ ViewGroup firstParent = (ViewGroup) getChildAt(0);
+ if (mRows == 0) {
+ return firstParent;
+ }
+ int column = (int) Math.floor(index / mRows);
+ ViewGroup parent = (ViewGroup) getChildAt(column);
+ return parent != null ? parent : firstParent;
+ }
+
+ /**
+ * Sets the expected number of items that this grid will be responsible for rendering.
+ */
+ public void setExpectedCount(int count) {
+ mExpectedCount = count;
+ mRows = getRowCount();
+ mColumns = getColumnCount();
+
+ for (int i = 0; i < getChildCount(); i++) {
+ if (i <= mColumns) {
+ setSublistVisibility(i, true);
+ } else {
+ setSublistVisibility(i, false);
+ }
+ }
+
+ }
+
+ private void setSublistVisibility(int index, boolean visible) {
+ View subList = getChildAt(index);
+ Log.d("ListGrid", "index: " + index + ", visibility: " + visible);
+ if (subList != null) {
+ subList.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private int getRowCount() {
+ return (int) Math.ceil(Math.sqrt(mExpectedCount));
+ }
+
+ private int getColumnCount() {
+ return (int) Math.round(Math.sqrt(mExpectedCount));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index b218e80693b1..2339fae2d1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -30,8 +30,12 @@ import com.android.systemui.Dependency
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
import com.android.systemui.R
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import javax.inject.Singleton
-class PrivacyItemController(val context: Context, val callback: Callback) {
+@Singleton
+class PrivacyItemController @Inject constructor(val context: Context) {
companion object {
val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
@@ -56,9 +60,10 @@ class PrivacyItemController(val context: Context, val callback: Callback) {
private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER)
private var listening = false
val systemApp = PrivacyApplication(context.getString(R.string.device_services), context)
+ private val callbacks = mutableListOf<WeakReference<Callback>>()
private val notifyChanges = Runnable {
- callback.privacyChanged(privacyList)
+ callbacks.forEach { it.get()?.privacyChanged(privacyList) }
}
private val updateListAndNotifyChanges = Runnable {
@@ -88,8 +93,8 @@ class PrivacyItemController(val context: Context, val callback: Callback) {
registerReceiver()
}
- init {
- registerReceiver()
+ private fun unregisterReceiver() {
+ context.unregisterReceiver(userSwitcherReceiver)
}
private fun registerReceiver() {
@@ -108,17 +113,41 @@ class PrivacyItemController(val context: Context, val callback: Callback) {
bgHandler.post(updateListAndNotifyChanges)
}
- fun setListening(listen: Boolean) {
+ @VisibleForTesting
+ internal fun setListening(listen: Boolean) {
if (listening == listen) return
listening = listen
if (listening) {
appOpsController.addCallback(OPS, cb)
+ registerReceiver()
update(true)
} else {
appOpsController.removeCallback(OPS, cb)
+ unregisterReceiver()
}
}
+ private fun addCallback(callback: WeakReference<Callback>) {
+ callbacks.add(callback)
+ if (callbacks.isNotEmpty() && !listening) setListening(true)
+ // Notify this callback if we didn't set to listening
+ else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
+ }
+
+ private fun removeCallback(callback: WeakReference<Callback>) {
+ // Removes also if the callback is null
+ callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
+ if (callbacks.isEmpty()) setListening(false)
+ }
+
+ fun addCallback(callback: Callback) {
+ addCallback(WeakReference(callback))
+ }
+
+ fun removeCallback(callback: Callback) {
+ removeCallback(WeakReference(callback))
+ }
+
private fun updatePrivacyList() {
privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
.mapNotNull { toPrivacyItem(it) }.distinct()
@@ -149,4 +178,13 @@ class PrivacyItemController(val context: Context, val callback: Callback) {
}
}
}
+
+ private class NotifyChangesToCallback(
+ private val callback: Callback?,
+ private val list: List<PrivacyItem>
+ ) : Runnable {
+ override fun run() {
+ callback?.privacyChanged(list)
+ }
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e63f88a898cc..c0ed4b97eaff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -30,14 +30,18 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
import android.os.UserManager;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -45,7 +49,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.CarrierText;
+import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.UserIconDrawable;
@@ -68,7 +72,11 @@ import javax.inject.Inject;
import javax.inject.Named;
public class QSFooterImpl extends FrameLayout implements QSFooter,
- OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback {
+ OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback,
+ CarrierTextController.CarrierTextCallback {
+
+ private static final int SIM_SLOTS = 2;
+ private static final String TAG = "QSFooterImpl";
private final ActivityStarter mActivityStarter;
private final UserInfoController mUserInfoController;
@@ -77,7 +85,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
- private CarrierText mCarrierText;
private boolean mQsDisabled;
private QSPanel mQsPanel;
@@ -99,12 +106,20 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private View mActionsContainer;
private View mDragHandle;
- private View mMobileGroup;
- private ImageView mMobileSignal;
- private ImageView mMobileRoaming;
+
+ private View mCarrierDivider;
+ private ViewGroup mMobileFooter;
+ private View[] mMobileGroups = new View[SIM_SLOTS];
+ private ViewGroup[] mCarrierGroups = new ViewGroup[SIM_SLOTS];
+ private TextView[] mCarrierTexts = new TextView[SIM_SLOTS];
+ private ImageView[] mMobileSignals = new ImageView[SIM_SLOTS];
+ private ImageView[] mMobileRoamings = new ImageView[SIM_SLOTS];
+ private final CellSignalState[] mInfos =
+ new CellSignalState[]{new CellSignalState(), new CellSignalState()};
+
private final int mColorForeground;
- private final CellSignalState mInfo = new CellSignalState();
private OnClickListener mExpandClickListener;
+ private CarrierTextController mCarrierTextController;
@Inject
public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -134,10 +149,20 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mSettingsContainer = findViewById(R.id.settings_button_container);
mSettingsButton.setOnClickListener(this);
- mMobileGroup = findViewById(R.id.mobile_combo);
- mMobileSignal = findViewById(R.id.mobile_signal);
- mMobileRoaming = findViewById(R.id.mobile_roaming);
- mCarrierText = findViewById(R.id.qs_carrier_text);
+ mMobileFooter = findViewById(R.id.qs_mobile);
+ mCarrierGroups[0] = findViewById(R.id.carrier1);
+ mCarrierGroups[1] = findViewById(R.id.carrier2);
+
+ for (int i = 0; i < SIM_SLOTS; i++) {
+ mMobileGroups[i] = mCarrierGroups[i].findViewById(R.id.mobile_combo);
+ mMobileSignals[i] = mCarrierGroups[i].findViewById(R.id.mobile_signal);
+ mMobileRoamings[i] = mCarrierGroups[i].findViewById(R.id.mobile_roaming);
+ mCarrierTexts[i] = mCarrierGroups[i].findViewById(R.id.qs_carrier_text);
+ }
+ mCarrierDivider = findViewById(R.id.qs_carrier_divider);
+ CharSequence separator = mContext.getString(
+ com.android.internal.R.string.kg_text_message_separator);
+ mCarrierTextController = new CarrierTextController(mContext, separator, false, false);
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -204,8 +229,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private TouchAnimator createFooterAnimator() {
return new TouchAnimator.Builder()
.addFloat(mDivider, "alpha", 0, 1)
- .addFloat(mCarrierText, "alpha", 0, 0, 1)
- .addFloat(mMobileGroup, "alpha", 0, 1)
+ .addFloat(mMobileFooter, "alpha", 0, 0, 1)
+ .addFloat(mCarrierDivider, "alpha", 0, 1)
.addFloat(mActionsContainer, "alpha", 0, 1)
.addFloat(mDragHandle, "alpha", 1, 0, 0)
.addFloat(mPageIndicator, "alpha", 0, 1)
@@ -332,10 +357,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mNetworkController.addEmergencyListener(this);
mNetworkController.addCallback(this);
}
+ mCarrierTextController.setListening(this);
} else {
mUserInfoController.removeCallback(this);
mNetworkController.removeEmergencyListener(this);
mNetworkController.removeCallback(this);
+ mCarrierTextController.setListening(null);
}
}
@@ -358,7 +385,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
if (v == mSettingsButton) {
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
// If user isn't setup just unlock the device and dump them back at SUW.
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ });
return;
}
MetricsLogger.action(mContext,
@@ -415,32 +443,64 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
private void handleUpdateState() {
- mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE);
- if (mInfo.visible) {
- mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE);
- mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground));
- SignalDrawable d = new SignalDrawable(mContext);
- d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
- mMobileSignal.setImageDrawable(d);
- mMobileSignal.setImageLevel(mInfo.mobileSignalIconId);
-
- StringBuilder contentDescription = new StringBuilder();
- if (mInfo.contentDescription != null) {
- contentDescription.append(mInfo.contentDescription).append(", ");
+ for (int i = 0; i < SIM_SLOTS; i++) {
+ mMobileGroups[i].setVisibility(mInfos[i].visible ? View.VISIBLE : View.GONE);
+ if (mInfos[i].visible) {
+ mMobileRoamings[i].setVisibility(mInfos[i].roaming ? View.VISIBLE : View.GONE);
+ mMobileRoamings[i].setImageTintList(ColorStateList.valueOf(mColorForeground));
+ SignalDrawable d = new SignalDrawable(mContext);
+ d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
+ mMobileSignals[i].setImageDrawable(d);
+ mMobileSignals[i].setImageLevel(mInfos[i].mobileSignalIconId);
+
+ StringBuilder contentDescription = new StringBuilder();
+ if (mInfos[i].contentDescription != null) {
+ contentDescription.append(mInfos[i].contentDescription).append(", ");
+ }
+ if (mInfos[i].roaming) {
+ contentDescription
+ .append(mContext.getString(R.string.data_connection_roaming))
+ .append(", ");
+ }
+ // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+ if (TextUtils.equals(mInfos[i].typeContentDescription,
+ mContext.getString(R.string.data_connection_no_internet))
+ || TextUtils.equals(mInfos[i].typeContentDescription,
+ mContext.getString(R.string.cell_data_off_content_description))) {
+ contentDescription.append(mInfos[i].typeContentDescription);
+ }
+ mMobileSignals[i].setContentDescription(contentDescription);
}
- if (mInfo.roaming) {
- contentDescription
- .append(mContext.getString(R.string.data_connection_roaming))
- .append(", ");
+ }
+ mCarrierDivider.setVisibility(
+ mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+ if (info.anySimReady) {
+ boolean[] slotSeen = new boolean[SIM_SLOTS];
+ for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+ int slot = SubscriptionManager.getSlotIndex(info.subscriptionIds[i]);
+ mInfos[slot].visible = true;
+ slotSeen[slot] = true;
+ mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim());
+ mCarrierGroups[slot].setVisibility(View.VISIBLE);
}
- // TODO: show mobile data off/no internet text for 5 seconds before carrier text
- if (TextUtils.equals(mInfo.typeContentDescription,
- mContext.getString(R.string.data_connection_no_internet))
- || TextUtils.equals(mInfo.typeContentDescription,
- mContext.getString(R.string.cell_data_off_content_description))) {
- contentDescription.append(mInfo.typeContentDescription);
+ for (int i = 0; i < SIM_SLOTS; i++) {
+ if (!slotSeen[i]) {
+ mInfos[i].visible = false;
+ mCarrierGroups[i].setVisibility(View.GONE);
+ }
}
- mMobileSignal.setContentDescription(contentDescription);
+ handleUpdateState();
+ } else {
+ mInfos[0].visible = false;
+ mInfos[1].visible = false;
+ mCarrierTexts[0].setText(info.carrierText);
+ mCarrierGroups[0].setVisibility(View.VISIBLE);
+ mCarrierGroups[1].setVisibility(View.GONE);
+ handleUpdateState();
}
}
@@ -450,18 +510,23 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
int qsType, boolean activityIn, boolean activityOut,
String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming) {
- mInfo.visible = statusIcon.visible;
- mInfo.mobileSignalIconId = statusIcon.icon;
- mInfo.contentDescription = statusIcon.contentDescription;
- mInfo.typeContentDescription = typeContentDescription;
- mInfo.roaming = roaming;
+ int slotIndex = SubscriptionManager.getSlotIndex(subId);
+ if (slotIndex >= SIM_SLOTS) {
+ Log.e(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+ }
+ mInfos[slotIndex].visible = statusIcon.visible;
+ mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
+ mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
+ mInfos[slotIndex].typeContentDescription = typeContentDescription;
+ mInfos[slotIndex].roaming = roaming;
handleUpdateState();
}
@Override
public void setNoSims(boolean hasNoSims, boolean simDetected) {
if (hasNoSims) {
- mInfo.visible = false;
+ mInfos[0].visible = false;
+ mInfos[1].visible = false;
}
handleUpdateState();
}
@@ -473,4 +538,38 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
String typeContentDescription;
boolean roaming;
}
+
+
+ /**
+ * TextView that changes its ellipsize value with its visibility.
+ */
+ public static class QSCarrierText extends TextView {
+ public QSCarrierText(Context context) {
+ super(context);
+ }
+
+ public QSCarrierText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ // Only show marquee when visible
+ if (visibility == VISIBLE) {
+ setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ } else {
+ setEllipsize(TextUtils.TruncateAt.END);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 75ab5dfd2977..2d64ecd99c88 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -180,14 +180,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
NextAlarmController nextAlarmController, ZenModeController zenModeController,
BatteryController batteryController, StatusBarIconController statusBarIconController,
- ActivityStarter activityStarter) {
+ ActivityStarter activityStarter, PrivacyItemController privacyItemController) {
super(context, attrs);
mAlarmController = nextAlarmController;
mZenController = zenModeController;
mBatteryController = batteryController;
mStatusBarIconController = statusBarIconController;
mActivityStarter = activityStarter;
- mPrivacyItemController = new PrivacyItemController(context, mPICCallback);
+ mPrivacyItemController = privacyItemController;
mShownCount = getStoredShownCount();
}
@@ -512,7 +512,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
return;
}
mHeaderQsPanel.setListening(listening);
- mPrivacyItemController.setListening(listening);
mListening = listening;
if (listening) {
@@ -520,9 +519,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mAlarmController.addCallback(this);
mContext.registerReceiver(mRingerReceiver,
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+ mPrivacyItemController.addCallback(mPICCallback);
} else {
mZenController.removeCallback(this);
mAlarmController.removeCallback(this);
+ mPrivacyItemController.removeCallback(mPICCallback);
mContext.unregisterReceiver(mRingerReceiver);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index b04132d09b7c..43ce0b50e03d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -82,7 +82,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
if ("1".equals(Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE))
&& mController.getAutoModeRaw() == -1) {
- mController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM);
+ mController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
Log.i("NightDisplayTile", "Enrolled in forced night display auto mode");
}
@@ -127,7 +127,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
@Nullable
private String getSecondaryLabel(boolean isNightLightActivated) {
switch(mController.getAutoMode()) {
- case ColorDisplayController.AUTO_MODE_TWILIGHT:
+ case ColorDisplayManager.AUTO_MODE_TWILIGHT:
// Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be
// turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset.
return isNightLightActivated
@@ -136,7 +136,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState>
: mContext.getString(
R.string.quick_settings_night_secondary_label_on_at_sunset);
- case ColorDisplayController.AUTO_MODE_CUSTOM:
+ case ColorDisplayManager.AUTO_MODE_CUSTOM_TIME:
// User-specified time, approximated to the nearest hour.
final @StringRes int toggleTimeStringRes;
final LocalTime toggleTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 6c3e504da8bd..04534ba06d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar;
import android.view.View;
import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/**
@@ -92,9 +93,15 @@ public class CrossFadeHelper {
private static void updateLayerType(View view, float alpha) {
if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) {
- view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
- view.setLayerType(View.LAYER_TYPE_NONE, null);
+ if (view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ view.setTag(R.id.cross_fade_layer_type_changed_tag, true);
+ }
+ } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE
+ && view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
+ if (view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
}
}
@@ -114,7 +121,7 @@ public class CrossFadeHelper {
.setStartDelay(delay)
.setInterpolator(Interpolators.ALPHA_IN)
.withEndAction(null);
- if (view.hasOverlappingRendering()) {
+ if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
view.animate().withLayer();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
new file mode 100644
index 000000000000..494473242a90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.statusbar
+
+import android.annotation.ColorInt
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Point
+import android.graphics.Rect
+import android.renderscript.Allocation
+import android.renderscript.Element
+import android.renderscript.RenderScript
+import android.renderscript.ScriptIntrinsicBlur
+import android.util.MathUtils
+import com.android.internal.graphics.ColorUtils
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val COLOR_ALPHA = (255 * 0.7f).toInt()
+private const val BLUR_RADIUS = 25f
+private const val DOWNSAMPLE = 6
+
+@Singleton
+class MediaArtworkProcessor @Inject constructor() {
+
+ private val mTmpSize = Point()
+ private var mArtworkCache: Bitmap? = null
+
+ fun processArtwork(context: Context, artwork: Bitmap, @ColorInt color: Int): Bitmap {
+ if (mArtworkCache != null) {
+ return mArtworkCache!!
+ }
+
+ context.display.getSize(mTmpSize)
+ val renderScript = RenderScript.create(context)
+ val rect = Rect(0, 0,artwork.width, artwork.height)
+ MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
+ val inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
+ true /* filter */)
+ val input = Allocation.createFromBitmap(renderScript, inBitmap,
+ Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
+ val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+ Bitmap.Config.ARGB_8888)
+ val output = Allocation.createFromBitmap(renderScript, outBitmap)
+ val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
+ blur.setRadius(BLUR_RADIUS)
+ blur.setInput(input)
+ blur.forEach(output)
+ output.copyTo(outBitmap)
+
+ input.destroy()
+ output.destroy()
+ inBitmap.recycle()
+
+ val canvas = Canvas(outBitmap)
+ canvas.drawColor(ColorUtils.setAlphaComponent(color, COLOR_ALPHA))
+ return outBitmap
+ }
+
+ fun clearCache() {
+ mArtworkCache?.recycle()
+ mArtworkCache = null
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index f46ded4d61d8..c25b7cfd804c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -39,6 +39,9 @@ public interface NotificationLockscreenUserManager {
boolean isCurrentProfile(int userId);
+ /** Adds a listener to be notified when the current user changes. */
+ void addUserChangedListener(UserChangedListener listener);
+
void destroy();
SparseArray<UserInfo> getCurrentProfiles();
@@ -58,4 +61,9 @@ public interface NotificationLockscreenUserManager {
boolean needsRedaction(NotificationEntry entry);
boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
+
+ /** Notified when the current user changes. */
+ interface UserChangedListener {
+ void onUserChanged(int userId);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index d2ce31d75648..4f9d4282dae8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -55,6 +55,8 @@ import com.android.systemui.statusbar.policy.KeyguardMonitor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* Handles keeping track of the current user, profiles, and various things related to hiding
@@ -78,6 +80,7 @@ public class NotificationLockscreenUserManagerImpl implements
private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
private final UserManager mUserManager;
private final IStatusBarService mBarService;
+ private final List<UserChangedListener> mListeners = new ArrayList<>();
private boolean mShowLockscreenNotifications;
private boolean mAllowLockscreenRemoteInput;
@@ -112,6 +115,10 @@ public class NotificationLockscreenUserManagerImpl implements
updatePublicMode();
mPresenter.onUserSwitched(mCurrentUserId);
getEntryManager().getNotificationData().filterAndSort();
+
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserChanged(mCurrentUserId);
+ }
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
updateCurrentProfilesCache();
} else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
@@ -502,6 +509,10 @@ public class NotificationLockscreenUserManagerImpl implements
}
}
+ @Override
+ public void addUserChangedListener(UserChangedListener listener) {
+ mListeners.add(listener);
+ }
// public void updatePublicMode() {
// //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
@@ -541,6 +552,7 @@ public class NotificationLockscreenUserManagerImpl implements
public void destroy() {
mContext.unregisterReceiver(mBaseBroadcastReceiver);
mContext.unregisterReceiver(mAllUsersReceiver);
+ mListeners.clear();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7412702abfea..98a3a547ed2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -102,6 +103,7 @@ public class NotificationMediaManager implements Dumpable {
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
private final ArrayList<MediaListener> mMediaListeners;
+ private final MediaArtworkProcessor mMediaArtworkProcessor;
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
@@ -133,6 +135,7 @@ public class NotificationMediaManager implements Dumpable {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
+ mMediaArtworkProcessor.clearCache();
mMediaMetadata = metadata;
dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
}
@@ -143,8 +146,10 @@ public class NotificationMediaManager implements Dumpable {
Context context,
Lazy<ShadeController> shadeController,
Lazy<StatusBarWindowController> statusBarWindowController,
- NotificationEntryManager notificationEntryManager) {
+ NotificationEntryManager notificationEntryManager,
+ MediaArtworkProcessor mediaArtworkProcessor) {
mContext = context;
+ mMediaArtworkProcessor = mediaArtworkProcessor;
mMediaListeners = new ArrayList<>();
mMediaSessionManager
= (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
@@ -366,6 +371,7 @@ public class NotificationMediaManager implements Dumpable {
}
private void clearCurrentMediaNotificationSession() {
+ mMediaArtworkProcessor.clearCache();
mMediaMetadata = null;
if (mMediaController != null) {
if (DEBUG_MEDIA) {
@@ -418,7 +424,19 @@ public class NotificationMediaManager implements Dumpable {
// might still be null
}
if (artworkBitmap != null) {
- artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), artworkBitmap);
+ int notificationColor;
+ synchronized (mEntryManager.getNotificationData()) {
+ NotificationEntry entry = mEntryManager.getNotificationData()
+ .get(mMediaNotificationKey);
+ if (entry == null || entry.getRow() == null) {
+ notificationColor = Color.TRANSPARENT;
+ } else {
+ notificationColor = entry.getRow().calculateBgColor();
+ }
+ }
+ Bitmap bmp = mMediaArtworkProcessor.processArtwork(mContext, artworkBitmap,
+ notificationColor);
+ artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
}
}
boolean allowWhenShade = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 8c29fb50f00a..54ed0d9cd8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -333,6 +333,7 @@ public class NotificationData {
mGroupManager.onEntryUpdated(entry, oldSbn);
}
entry.populateFromRanking(mTmpRanking);
+ entry.setIsHighPriority(isHighPriority(entry.notification));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index ee551ee96e7b..2e93c3822737 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -153,6 +153,12 @@ public final class NotificationEntry {
*/
private boolean mUserDismissedBubble;
+ /**
+ * Whether this notification is shown to the user as a high priority notification: visible on
+ * the lock screen/status bar and in the top section in the shade.
+ */
+ private boolean mHighPriority;
+
public NotificationEntry(StatusBarNotification n) {
this(n, null);
}
@@ -191,6 +197,14 @@ public final class NotificationEntry {
return interruption;
}
+ public boolean isHighPriority() {
+ return mHighPriority;
+ }
+
+ public void setIsHighPriority(boolean highPriority) {
+ this.mHighPriority = highPriority;
+ }
+
public void setIsBubble(boolean bubbleable) {
mIsBubble = bubbleable;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 7b94c7450637..35b7ef42177d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -167,13 +167,10 @@ public class NotificationLogger implements StateListener {
*/
public static NotificationVisibility.NotificationLocation getNotificationLocation(
NotificationEntry entry) {
- ExpandableNotificationRow row = entry.getRow();
- ExpandableViewState childViewState = row.getViewState();
-
- if (childViewState == null) {
+ if (entry == null || entry.getRow() == null || entry.getRow().getViewState() == null) {
return NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN;
}
- return convertNotificationLocation(childViewState.location);
+ return convertNotificationLocation(entry.getRow().getViewState().location);
}
private static NotificationVisibility.NotificationLocation convertNotificationLocation(
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 296c061459bc..bed2426021a1 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
@@ -3092,6 +3092,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ /** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */
+ public void setDismissRtl(boolean dismissRtl) {
+ mMenuRow.setDismissRtl(dismissRtl);
+ }
+
private static class NotificationViewState extends ExpandableViewState {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 5e9f20795272..cb1384cacec8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -300,7 +300,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
row.getIsNonblockable(),
isForBlockingHelper,
row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE,
- row.getEntry().importance);
+ row.getEntry().importance,
+ row.getEntry().isHighPriority());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 5253e38fd675..2a9a815e12d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -21,7 +21,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -97,8 +96,12 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private int mNumUniqueChannelsInRow;
private NotificationChannel mSingleNotificationChannel;
private int mStartingChannelImportance;
- private int mStartingChannelOrNotificationImportance;
- private int mChosenImportance;
+ private boolean mWasShownHighPriority;
+ /**
+ * The last importance level chosen by the user. Null if the user has not chosen an importance
+ * level; non-null once the user takes an action which indicates an explicit preference.
+ */
+ @Nullable private Integer mChosenImportance;
private boolean mIsSingleDefaultChannel;
private boolean mIsNonblockable;
private StatusBarNotification mSbn;
@@ -195,13 +198,14 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
final OnAppSettingsClickListener onAppSettingsClick,
boolean isDeviceProvisioned,
boolean isNonblockable,
- int importance)
+ int importance,
+ boolean wasShownHighPriority)
throws RemoteException {
bindNotification(pm, iNotificationManager, pkg, notificationChannel,
numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
onAppSettingsClick, isDeviceProvisioned, isNonblockable,
false /* isBlockingHelper */, false /* isUserSentimentNegative */,
- importance);
+ importance, wasShownHighPriority);
}
public void bindNotification(
@@ -218,7 +222,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
boolean isNonblockable,
boolean isForBlockingHelper,
boolean isUserSentimentNegative,
- int importance)
+ int importance,
+ boolean wasShownHighPriority)
throws RemoteException {
mINotificationManager = iNotificationManager;
mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -231,10 +236,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mCheckSaveListener = checkSaveListener;
mOnSettingsClickListener = onSettingsClick;
mSingleNotificationChannel = notificationChannel;
- int channelImportance = mSingleNotificationChannel.getImportance();
- mStartingChannelImportance = mChosenImportance = channelImportance;
- mStartingChannelOrNotificationImportance =
- channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance;
+ mStartingChannelImportance = mSingleNotificationChannel.getImportance();
+ mWasShownHighPriority = wasShownHighPriority;
mNegativeUserSentiment = isUserSentimentNegative;
mIsNonblockable = isNonblockable;
mIsForeground =
@@ -400,19 +403,27 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
* @return new LogMaker
*/
private LogMaker importanceChangeLogMaker() {
+ Integer chosenImportance =
+ mChosenImportance != null ? mChosenImportance : mStartingChannelImportance;
return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE)
.setType(MetricsEvent.TYPE_ACTION)
- .setSubtype(mChosenImportance - mStartingChannelImportance);
+ .setSubtype(chosenImportance - mStartingChannelImportance);
}
private boolean hasImportanceChanged() {
return mSingleNotificationChannel != null
- && mStartingChannelImportance != mChosenImportance;
+ && mChosenImportance != null
+ && (mStartingChannelImportance != mChosenImportance
+ || (mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT)
+ || (!mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT));
}
private void saveImportance() {
if (!mIsNonblockable
|| mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
+ if (mChosenImportance == null) {
+ mChosenImportance = mStartingChannelImportance;
+ }
updateImportance();
}
}
@@ -421,12 +432,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
* Commits the updated importance values on the background thread.
*/
private void updateImportance() {
- mMetricsLogger.write(importanceChangeLogMaker());
-
- Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
- mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
- mStartingChannelImportance, mChosenImportance));
+ if (mChosenImportance != null) {
+ mMetricsLogger.write(importanceChangeLogMaker());
+
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
+ mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
+ mStartingChannelImportance, mChosenImportance));
+ }
}
private void bindButtons() {
@@ -444,11 +458,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
TextView silent = findViewById(R.id.int_silent);
TextView alert = findViewById(R.id.int_alert);
- boolean isCurrentlyAlerting =
- mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT;
-
block.setOnClickListener(mOnStopOrMinimizeNotifications);
- if (isCurrentlyAlerting) {
+ if (mWasShownHighPriority) {
silent.setOnClickListener(mOnToggleSilent);
silent.setText(R.string.inline_silent_button_silent);
alert.setOnClickListener(mOnKeepShowing);
@@ -517,7 +528,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
break;
case ACTION_TOGGLE_SILENT:
mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
- if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
+ if (mWasShownHighPriority) {
mChosenImportance = IMPORTANCE_LOW;
confirmationText.setText(R.string.notification_channel_silenced);
} else {
@@ -584,9 +595,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
@Override
public void onFinishedClosing() {
- mStartingChannelImportance = mChosenImportance;
- if (mChosenImportance != IMPORTANCE_UNSPECIFIED) {
- mStartingChannelOrNotificationImportance = mChosenImportance;
+ if (mChosenImportance != null) {
+ mStartingChannelImportance = mChosenImportance;
}
mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index d97162c2ef1e..d83a158b319f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -23,7 +23,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.app.Notification;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -78,6 +77,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private ArrayList<MenuItem> mRightMenuItems;
private final Map<View, MenuItem> mMenuItemsByView = new ArrayMap<>();
private OnMenuEventListener mMenuListener;
+ private boolean mDismissRtl;
+ private boolean mIsForeground;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -238,6 +239,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
private void createMenuViews(boolean resetState, final boolean isForeground) {
+ mIsForeground = isForeground;
+
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
@@ -250,12 +253,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
mAppOpsItem = createAppOpsItem(mContext);
if (NotificationUtils.useNewInterruptionModel(mContext)) {
- int channelImportance = mParent.getEntry().channel.getImportance();
- int effectiveImportance =
- channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED
- ? mParent.getEntry().importance : channelImportance;
- mInfoItem = createInfoItem(mContext,
- effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT);
+ mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
} else {
mInfoItem = createInfoItem(mContext);
}
@@ -268,10 +266,11 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mRightMenuItems.add(mAppOpsItem);
mLeftMenuItems.addAll(mRightMenuItems);
} else {
- mRightMenuItems.add(mInfoItem);
- mRightMenuItems.add(mAppOpsItem);
+ ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
+ menuItems.add(mInfoItem);
+ menuItems.add(mAppOpsItem);
if (!isForeground) {
- mRightMenuItems.add(mSnoozeItem);
+ menuItems.add(mSnoozeItem);
}
}
@@ -729,6 +728,14 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
return getParent().canViewBeDismissed();
}
+ @Override
+ public void setDismissRtl(boolean dismissRtl) {
+ mDismissRtl = dismissRtl;
+ if (mMenuContainer != null) {
+ createMenuViews(true, mIsForeground);
+ }
+ }
+
public static class NotificationMenuItem implements MenuItem {
View mMenuView;
GutsContent mGutsContent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
index db7b4fc189aa..4bdc1705c5ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
@@ -17,8 +17,15 @@
package com.android.systemui.statusbar.notification.row.wrapper;
import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.os.Build;
import android.view.View;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -42,6 +49,47 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
}
@Override
+ public void onReinflated() {
+ super.onReinflated();
+
+ Configuration configuration = mView.getResources().getConfiguration();
+ boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+
+ float[] hsl = new float[] {0f, 0f, 0f};
+ ColorUtils.colorToHSL(mBackgroundColor, hsl);
+ boolean backgroundIsDark = Color.alpha(mBackgroundColor) == 0
+ || hsl[1] == 0 && hsl[2] < 0.5;
+ boolean backgroundHasColor = hsl[1] > 0;
+
+ // Let's invert the notification colors when we're in night mode and
+ // the notification background isn't colorized.
+ if (!backgroundIsDark && !backgroundHasColor && nightMode
+ && mRow.getEntry().targetSdk < Build.VERSION_CODES.Q) {
+ Paint paint = new Paint();
+ ColorMatrix matrix = new ColorMatrix();
+ ColorMatrix tmp = new ColorMatrix();
+ // Inversion should happen on Y'UV space to conseve the colors and
+ // only affect the luminosity.
+ matrix.setRGB2YUV();
+ tmp.set(new float[]{
+ -1f, 0f, 0f, 0f, 255f,
+ 0f, 1f, 0f, 0f, 0f,
+ 0f, 0f, 1f, 0f, 0f,
+ 0f, 0f, 0f, 1f, 0f
+ });
+ matrix.postConcat(tmp);
+ tmp.setYUV2RGB();
+ matrix.postConcat(tmp);
+ paint.setColorFilter(new ColorMatrixColorFilter(matrix));
+ mView.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
+
+ hsl[2] = 1f - hsl[2];
+ mBackgroundColor = ColorUtils.HSLToColor(hsl);
+ }
+ }
+
+ @Override
protected boolean shouldClearBackgroundOnReapply() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1efdc56874f3..9258c9971451 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -37,7 +37,7 @@ public abstract class NotificationViewWrapper implements TransformableView {
protected final View mView;
protected final ExpandableNotificationRow mRow;
- private int mBackgroundColor = 0;
+ protected int mBackgroundColor = 0;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2129b81b1448..63b34d185c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -174,6 +174,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private final boolean mShouldDrawNotificationBackground;
private boolean mLowPriorityBeforeSpeedBump;
private final boolean mAllowLongPress;
+ private boolean mDismissRtl;
private float mExpandedHeight;
private int mOwnScrollY;
@@ -533,8 +534,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
tunerService.addTunable((key, newValue) -> {
if (key.equals(LOW_PRIORITY)) {
mLowPriorityBeforeSpeedBump = "1".equals(newValue);
+ } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
+ updateDismissRtlSetting("1".equals(newValue));
}
- }, LOW_PRIORITY);
+ }, LOW_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -548,6 +551,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
});
}
+ private void updateDismissRtlSetting(boolean dismissRtl) {
+ mDismissRtl = dismissRtl;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
+ }
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onFinishInflate() {
@@ -2600,8 +2613,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
View child = getChildAt(i);
if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (!mEntryManager.getNotificationData().isHighPriority(
- row.getStatusBarNotification())) {
+ if (!row.getEntry().isHighPriority()) {
break;
} else {
lastChildBeforeGap = row;
@@ -2619,8 +2631,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
View child = getChildAt(i);
if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (!mEntryManager.getNotificationData().isHighPriority(
- row.getStatusBarNotification())) {
+ if (!row.getEntry().isHighPriority()) {
return row;
}
}
@@ -3255,6 +3266,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
+ }
if (ANCHOR_SCROLLING) {
// TODO: once we're recycling this will need to check the adapter position of the child
if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
@@ -5756,11 +5770,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
currentIndex++;
boolean beforeSpeedBump;
if (mLowPriorityBeforeSpeedBump) {
- beforeSpeedBump = !mEntryManager.getNotificationData().isAmbient(
- row.getStatusBarNotification().getKey());
+ beforeSpeedBump = !row.getEntry().ambient;
} else {
- beforeSpeedBump = mEntryManager.getNotificationData().isHighPriority(
- row.getStatusBarNotification());
+ beforeSpeedBump = row.getEntry().isHighPriority();
}
if (beforeSpeedBump) {
speedBumpIndex = currentIndex;
@@ -5784,8 +5796,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
continue;
}
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
- if (!mEntryManager.getNotificationData().isHighPriority(
- row.getStatusBarNotification())) {
+ if (!row.getEntry().isHighPriority()) {
if (currentIndex > 0) {
gapIndex = currentIndex;
}
@@ -6315,7 +6326,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
boolean isValidDirection;
if (NotificationUtils.useNewInterruptionModel(mContext)) {
- isValidDirection = isLayoutRtl() ? !isRightOrDown : isRightOrDown;
+ isValidDirection = mDismissRtl ? !isRightOrDown : isRightOrDown;
} else {
isValidDirection = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index fac4dbbe735a..b96c55b37c48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -168,8 +168,8 @@ public class AutoTileManager {
@Override
public void onAutoModeChanged(int autoMode) {
- if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
- || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
+ if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
+ || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
addNightTile();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 03375d20e6db..e953ad53527b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -113,6 +113,7 @@ public class KeyguardStatusBarView extends RelativeLayout
* Ratio representing being in ambient mode or not.
*/
private float mDarkAmount;
+ private boolean mDozing;
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -210,7 +211,7 @@ public class KeyguardStatusBarView extends RelativeLayout
mMultiUserSwitch.setVisibility(View.GONE);
}
}
- mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+ mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable || mDozing);
}
private void updateSystemIconsLayoutParams() {
@@ -347,7 +348,7 @@ public class KeyguardStatusBarView extends RelativeLayout
mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
onThemeChanged();
- updateDozeState();
+ updateDarkState();
}
@Override
@@ -506,21 +507,29 @@ public class KeyguardStatusBarView extends RelativeLayout
}
}
+ public void setDozing(boolean dozing) {
+ if (mDozing == dozing) {
+ return;
+ }
+ mDozing = dozing;
+ updateVisibilities();
+ }
+
public void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
if (darkAmount == 0) {
dozeTimeTick();
}
- updateDozeState();
+ updateDarkState();
}
public void dozeTimeTick() {
mCurrentBurnInOffsetX = getBurnInOffset(mBurnInOffset, true /* xAxis */);
mCurrentBurnInOffsetY = getBurnInOffset(mBurnInOffset, false /* xAxis */);
- updateDozeState();
+ updateDarkState();
}
- private void updateDozeState() {
+ private void updateDarkState() {
float alpha = 1f - mDarkAmount;
int visibility = alpha != 0f ? VISIBLE : INVISIBLE;
mCarrierLabel.setAlpha(alpha * alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
index a5d938216f5c..39fbbb17c498 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
@@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.statusbar.phone.NavigationPrototypeController.PROTOTYPE_ENABLED;
import android.annotation.NonNull;
import android.content.Context;
@@ -84,7 +85,7 @@ public abstract class NavigationGestureAction {
// Tell launcher that this action requires a stable task list or not
boolean flag = requiresStableTaskList();
- if (flag != sLastTaskStabilizationFlag) {
+ if (getGlobalBoolean(PROTOTYPE_ENABLED) && flag != sLastTaskStabilizationFlag) {
Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(),
ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0);
sLastTaskStabilizationFlag = flag;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index a09e5858d576..f762a6a68ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -37,6 +37,7 @@ public class NavigationPrototypeController extends ContentObserver {
private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
+ public static final String PROTOTYPE_ENABLED = "prototype_enabled";
@Retention(RetentionPolicy.SOURCE)
@IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 077fcda70f0c..e86996a81bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -194,8 +194,7 @@ public class NotificationIconAreaController implements DarkReceiver,
if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
return false;
}
- if (!showLowPriority
- && !mEntryManager.getNotificationData().isHighPriority(entry.notification)) {
+ if (!showLowPriority && !entry.isHighPriority()) {
return false;
}
if (!entry.isTopLevelChild()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 0d5ebb9b6578..c10837165c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -140,7 +140,8 @@ public class NotificationPanelView extends PanelView implements
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
- private KeyguardStatusBarView mKeyguardStatusBar;
+ @VisibleForTesting
+ protected KeyguardStatusBarView mKeyguardStatusBar;
private ViewGroup mBigClockContainer;
private QS mQs;
private FrameLayout mQsFrame;
@@ -2792,6 +2793,7 @@ public class NotificationPanelView extends PanelView implements
if (mDozing) {
mNotificationStackScroller.setShowDarkShelf(!hasCustomClock());
}
+ mKeyguardStatusBar.setDozing(mDozing);
if (mBarState == StatusBarState.KEYGUARD
|| mBarState == StatusBarState.SHADE_LOCKED) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 43c35f11d9e5..18711c0d1ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -173,7 +173,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mProvisionedController = Dependency.get(DeviceProvisionedController.class);
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
mLocationController = Dependency.get(LocationController.class);
- mPrivacyItemController = new PrivacyItemController(mContext, this);
+ mPrivacyItemController = Dependency.get(PrivacyItemController.class);
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -266,7 +266,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mNextAlarmController.addCallback(mNextAlarmCallback);
mDataSaver.addCallback(this);
mKeyguardMonitor.addCallback(this);
- mPrivacyItemController.setListening(true);
+ mPrivacyItemController.addCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
@@ -294,7 +294,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mNextAlarmController.removeCallback(mNextAlarmCallback);
mDataSaver.removeCallback(this);
mKeyguardMonitor.removeCallback(this);
- mPrivacyItemController.setListening(false);
+ mPrivacyItemController.removeCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallback(this);
mContext.unregisterReceiver(mIntentReceiver);
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 60a20cf15cc0..e80275793b28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -61,7 +61,6 @@ public class BubbleControllerTest extends SysuiTestCase {
private IActivityManager mActivityManager;
@Mock
private DozeParameters mDozeParameters;
- @Mock
private FrameLayout mStatusBarView;
@Captor
private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -80,6 +79,7 @@ public class BubbleControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mStatusBarView = new FrameLayout(mContext);
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
// Bubbles get added to status bar window view
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
new file mode 100644
index 000000000000..1bb7ef4a657b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.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 com.android.systemui.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+
+ @Spy
+ private ExpandedAnimationController mExpandedController = new ExpandedAnimationController();
+
+ private int mStackOffset;
+ private float mBubblePadding;
+ private float mBubbleSize;
+
+ private PointF mExpansionPoint;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ addOneMoreThanRenderLimitBubbles();
+ mLayout.setController(mExpandedController);
+ Resources res = mLayout.getResources();
+ mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+ mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ }
+
+ @Test
+ public void testExpansionAndCollapse() throws InterruptedException {
+ mExpansionPoint = new PointF(100, 100);
+ Runnable afterExpand = Mockito.mock(Runnable.class);
+ mExpandedController.expandFromStack(mExpansionPoint, afterExpand);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ testExpanded();
+ Mockito.verify(afterExpand).run();
+
+ Runnable afterCollapse = Mockito.mock(Runnable.class);
+ mExpandedController.collapseBackToStack(afterCollapse);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
+ Mockito.verify(afterExpand).run();
+ }
+
+ /** Check that children are in the correct positions for being stacked. */
+ private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ // Make sure the rest of the stack moved again, including the first bubble not moving, and
+ // is stacked to the right now that we're on the right side of the screen.
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
+ assertEquals(x + i * offsetMultiplier * mStackOffset,
+ mViews.get(i).getTranslationX(), 2f);
+ assertEquals(y, mViews.get(i).getTranslationY(), 2f);
+ }
+ }
+
+ /** Check that children are in the correct positions for being expanded. */
+ private void testExpanded() {
+ // Make sure the rest of the stack moved again, including the first bubble not moving, and
+ // is stacked to the right now that we're on the right side of the screen.
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
+ assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)),
+ mViews.get(i).getTranslationX(),
+ 2f);
+ assertEquals(mBubblePadding + mCutoutInsetSize,
+ mViews.get(i).getTranslationY(), 2f);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
new file mode 100644
index 000000000000..bfc02d902416
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -0,0 +1,471 @@
+/*
+ * 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.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+
+import android.os.SystemClock;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.google.android.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */
+public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
+ static final float TEST_TRANSLATION_X_OFFSET = 15f;
+
+ @Spy
+ private TestableAnimationController mTestableController = new TestableAnimationController();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // By default, use translation animations, chain the X animations with the default
+ // offset, and don't actually remove views immediately (since most implementations will wait
+ // to animate child views out before actually removing them).
+ mTestableController.setAnimatedProperties(Sets.newHashSet(
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+ mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X));
+ mTestableController.setOffsetForProperty(
+ DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET);
+ mTestableController.setRemoveImmediately(false);
+ }
+
+ @Test
+ public void testRenderVisibility() {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+
+ // The last child should be GONE, the rest VISIBLE.
+ for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+ assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE,
+ mLayout.getChildAt(i).getVisibility());
+ }
+ }
+
+ @Test
+ public void testHierarchyChanges() {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+
+ // Make sure the controller was notified of all the views we added.
+ for (View mView : mViews) {
+ Mockito.verify(mTestableController).onChildAdded(mView, 0);
+ }
+
+ // Remove some views and ensure the controller was notified, with the proper indices.
+ mTestableController.setRemoveImmediately(true);
+ mLayout.removeView(mViews.get(1));
+ mLayout.removeView(mViews.get(2));
+ Mockito.verify(mTestableController).onChildToBeRemoved(
+ eq(mViews.get(1)), eq(1), any());
+ Mockito.verify(mTestableController).onChildToBeRemoved(
+ eq(mViews.get(2)), eq(1), any());
+
+ // Make sure we still get view added notifications after doing some removals.
+ final View newBubble = new FrameLayout(mContext);
+ mLayout.addView(newBubble, 0);
+ Mockito.verify(mTestableController).onChildAdded(newBubble, 0);
+ }
+
+ @Test
+ public void testUpdateValueNotChained() throws InterruptedException {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+
+ // Don't chain any values.
+ mTestableController.setChainedProperties(Sets.newHashSet());
+
+ // Child views should not be translated.
+ assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+ assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+
+ // Animate the first child's translation X.
+ final CountDownLatch animLatch = new CountDownLatch(1);
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 100,
+ animLatch::countDown);
+ animLatch.await(1, TimeUnit.SECONDS);
+
+ // Ensure that the first view has been translated, but not the second one.
+ assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f);
+ assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+ }
+
+ @Test
+ public void testUpdateValueXChained() throws InterruptedException {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+ testChainedTranslationAnimations();
+ }
+
+ @Test
+ public void testSetEndListeners() throws InterruptedException {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+ mTestableController.setChainedProperties(Sets.newHashSet());
+
+ final CountDownLatch xLatch = new CountDownLatch(1);
+ OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ xLatch.countDown();
+ }
+ });
+
+ final CountDownLatch yLatch = new CountDownLatch(1);
+ final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ yLatch.countDown();
+ }
+ });
+
+ // Set end listeners for both x and y.
+ mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+ mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y);
+
+ // Animate x, and wait for it to finish.
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 100);
+ xLatch.await();
+ yLatch.await(1, TimeUnit.SECONDS);
+
+ // Make sure the x end listener was called only one time, and the y listener was never
+ // called since we didn't animate y. Wait 1 second after the original animation end trigger
+ // to make sure it doesn't get called again.
+ Mockito.verify(xEndListener, Mockito.after(1000).times(1))
+ .onAnimationEnd(
+ any(),
+ eq(false),
+ eq(100f),
+ anyFloat());
+ Mockito.verify(yEndListener, Mockito.after(1000).never())
+ .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat());
+ }
+
+ @Test
+ public void testRemoveEndListeners() throws InterruptedException {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+ mTestableController.setChainedProperties(Sets.newHashSet());
+
+ final CountDownLatch xLatch = new CountDownLatch(1);
+ OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ xLatch.countDown();
+ }
+ });
+
+ // Set the end listener.
+ mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+
+ // Animate x, and wait for it to finish.
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 100);
+ xLatch.await();
+
+ InOrder endListenerCalls = inOrder(xEndListener);
+ endListenerCalls.verify(xEndListener, Mockito.times(1))
+ .onAnimationEnd(
+ any(),
+ eq(false),
+ eq(100f),
+ anyFloat());
+
+ // Animate X again, remove the end listener.
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 1000);
+ mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
+ xLatch.await(1, TimeUnit.SECONDS);
+
+ // Make sure the end listener was not called.
+ endListenerCalls.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testPrecedingNonRemovedIndex() {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+
+ // Call removeView at index 4, but don't actually remove it yet (as if we're animating it
+ // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since
+ // 4 is on its way out.
+ assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3));
+ mLayout.removeView(mViews.get(4));
+ assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3));
+
+ // Call removeView at index 1, and actually remove it immediately. With the old view at 1
+ // instantly gone, the preceding view to 0 should be 1 in both cases.
+ assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0));
+ mTestableController.setRemoveImmediately(true);
+ mLayout.removeView(mViews.get(1));
+ assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0));
+ }
+
+ @Test
+ public void testSetController() throws InterruptedException {
+ // Add the bubbles, then set the controller, to make sure that a controller added to an
+ // already-initialized view works correctly.
+ addOneMoreThanRenderLimitBubbles();
+ mLayout.setController(mTestableController);
+ testChainedTranslationAnimations();
+
+ TestableAnimationController secondController =
+ Mockito.spy(new TestableAnimationController());
+ secondController.setAnimatedProperties(Sets.newHashSet(
+ DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y));
+ secondController.setChainedProperties(Sets.newHashSet(
+ DynamicAnimation.SCALE_X));
+ secondController.setOffsetForProperty(
+ DynamicAnimation.SCALE_X, 10f);
+ secondController.setRemoveImmediately(true);
+
+ mLayout.setController(secondController);
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.SCALE_X,
+ 0,
+ 1.5f);
+
+ waitForPropertyAnimations(DynamicAnimation.SCALE_X);
+
+ // Make sure we never asked the original controller about any SCALE animations, that would
+ // mean the controller wasn't switched over properly.
+ Mockito.verify(mTestableController, Mockito.never())
+ .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt());
+ Mockito.verify(mTestableController, Mockito.never())
+ .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
+
+ // Make sure we asked the new controller about its animated properties, and configuration
+ // options.
+ Mockito.verify(secondController, Mockito.atLeastOnce())
+ .getAnimatedProperties();
+ Mockito.verify(secondController, Mockito.atLeastOnce())
+ .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt());
+ Mockito.verify(secondController, Mockito.atLeastOnce())
+ .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
+
+ mLayout.setController(mTestableController);
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 100f);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+ // Make sure we never asked the second controller about the TRANSLATION_X animation.
+ Mockito.verify(secondController, Mockito.never())
+ .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt());
+ Mockito.verify(secondController, Mockito.never())
+ .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X));
+
+ }
+
+ @Test
+ public void testArePropertiesAnimating() throws InterruptedException {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+
+ assertFalse(mLayout.arePropertiesAnimating(
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 100);
+
+ // Wait for the animations to get underway.
+ SystemClock.sleep(50);
+
+ assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X));
+ assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y));
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+ assertFalse(mLayout.arePropertiesAnimating(
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+ }
+
+ @Test
+ public void testCancelAllAnimations() throws InterruptedException {
+ mLayout.setController(mTestableController);
+ addOneMoreThanRenderLimitBubbles();
+
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 1000);
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_Y,
+ 0,
+ 1000);
+
+ mLayout.cancelAllAnimations();
+
+ waitForLayoutMessageQueue();
+
+ // Animations should be somewhere before their end point.
+ assertTrue(mViews.get(0).getTranslationX() < 1000);
+ assertTrue(mViews.get(0).getTranslationY() < 1000);
+ }
+
+
+ /** Standard test of chained translation animations. */
+ private void testChainedTranslationAnimations() throws InterruptedException {
+ assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+ assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_X,
+ 0,
+ 100);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+ // Since we enabled chaining, animating the first view to 100 should animate the second to
+ // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble
+ // not being visible, or animated, make sure that it has the appropriate chained
+ // translation.
+ for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+ assertEquals(
+ 100 + i * TEST_TRANSLATION_X_OFFSET,
+ mLayout.getChildAt(i).getTranslationX(), .1f);
+ }
+
+ // Ensure that the Y translations were unaffected.
+ assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f);
+ assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f);
+
+ // Animate the first child's Y translation.
+ mLayout.animateValueForChildAtIndex(
+ DynamicAnimation.TRANSLATION_Y,
+ 0,
+ 100);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y);
+
+ // Ensure that only the first view's Y translation chained, since we only chained X
+ // translations.
+ assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f);
+ assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f);
+ }
+
+ /**
+ * Animation controller with configuration methods whose return values can be set by individual
+ * tests.
+ */
+ private class TestableAnimationController
+ extends PhysicsAnimationLayout.PhysicsAnimationController {
+ private Set<DynamicAnimation.ViewProperty> mAnimatedProperties = new HashSet<>();
+ private Set<DynamicAnimation.ViewProperty> mChainedProperties = new HashSet<>();
+ private HashMap<DynamicAnimation.ViewProperty, Float> mOffsetForProperty = new HashMap<>();
+ private boolean mRemoveImmediately = false;
+
+ void setAnimatedProperties(
+ Set<DynamicAnimation.ViewProperty> animatedProperties) {
+ mAnimatedProperties = animatedProperties;
+ }
+
+ void setChainedProperties(
+ Set<DynamicAnimation.ViewProperty> chainedProperties) {
+ mChainedProperties = chainedProperties;
+ }
+
+ void setOffsetForProperty(
+ DynamicAnimation.ViewProperty property, float offset) {
+ mOffsetForProperty.put(property, offset);
+ }
+
+ public void setRemoveImmediately(boolean removeImmediately) {
+ mRemoveImmediately = removeImmediately;
+ }
+
+ @Override
+ Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+ return mAnimatedProperties;
+ }
+
+ @Override
+ int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+ return mChainedProperties.contains(property) ? index + 1 : NONE;
+ }
+
+ @Override
+ float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+ return mOffsetForProperty.getOrDefault(property, 0f);
+ }
+
+ @Override
+ SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+ return new SpringForce();
+ }
+
+ @Override
+ void onChildAdded(View child, int index) {}
+
+ @Override
+ void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+ if (mRemoveImmediately) {
+ actuallyRemove.run();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
new file mode 100644
index 000000000000..186a76219536
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -0,0 +1,190 @@
+/*
+ * 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.animation;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a
+ * testable version of the layout, and provides some helpful methods to add views to the layout and
+ * wait for physics animations to finish running.
+ *
+ * See physics-animation-testing.md.
+ */
+public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
+ TestablePhysicsAnimationLayout mLayout;
+ List<View> mViews = new ArrayList<>();
+
+ Handler mMainThreadHandler;
+
+ int mMaxRenderedBubbles;
+ int mSystemWindowInsetSize = 50;
+ int mCutoutInsetSize = 100;
+
+ int mWidth = 1000;
+ int mHeight = 1000;
+
+ @Mock
+ private WindowInsets mWindowInsets;
+
+ @Mock
+ private DisplayCutout mCutout;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mLayout = new TestablePhysicsAnimationLayout(mContext);
+ mLayout.setLeft(0);
+ mLayout.setRight(mWidth);
+ mLayout.setTop(0);
+ mLayout.setBottom(mHeight);
+
+ mMaxRenderedBubbles =
+ getContext().getResources().getInteger(R.integer.bubbles_max_rendered);
+ mMainThreadHandler = new Handler(Looper.getMainLooper());
+
+ when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize);
+ when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize);
+ when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize);
+ when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize);
+
+ when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout);
+ when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize);
+ when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize);
+ when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize);
+ when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize);
+ }
+
+ /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */
+ void addOneMoreThanRenderLimitBubbles() {
+ for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+ final View newView = new FrameLayout(mContext);
+ mLayout.addView(newView, 0);
+ mViews.add(0, newView);
+
+ newView.setTranslationX(0);
+ newView.setTranslationY(0);
+ }
+ }
+
+ /**
+ * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties'
+ * animations to finish before allowing the test to proceed.
+ */
+ void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties)
+ throws InterruptedException {
+ final CountDownLatch animLatch = new CountDownLatch(properties.length);
+ for (DynamicAnimation.ViewProperty property : properties) {
+ mLayout.setTestEndListenerForProperty(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+ float value,
+ float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ animLatch.countDown();
+ }
+ }, property);
+ }
+ animLatch.await(1, TimeUnit.SECONDS);
+ }
+
+ /** Uses a latch to wait for the message queue to finish. */
+ void waitForLayoutMessageQueue() throws InterruptedException {
+ // Wait for layout, then the view should be actually removed.
+ CountDownLatch layoutLatch = new CountDownLatch(1);
+ mLayout.post(layoutLatch::countDown);
+ layoutLatch.await(1, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations
+ * are run on the main thread, which is a requirement of DynamicAnimation.
+ */
+ protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout {
+ public TestablePhysicsAnimationLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setController(PhysicsAnimationController controller) {
+ mMainThreadHandler.post(() -> super.setController(controller));
+ try {
+ waitForLayoutMessageQueue();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void cancelAllAnimations() {
+ mMainThreadHandler.post(super::cancelAllAnimations);
+ }
+
+ @Override
+ protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property,
+ int index, float value, float startVel, Runnable after) {
+ mMainThreadHandler.post(() ->
+ super.animateValueForChildAtIndex(property, index, value, startVel, after));
+ }
+
+ @Override
+ public WindowInsets getRootWindowInsets() {
+ return mWindowInsets;
+ }
+
+ /**
+ * Sets an end listener that will be called after the 'real' end listener that was already
+ * set.
+ */
+ private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener,
+ DynamicAnimation.ViewProperty property) {
+ final DynamicAnimation.OnAnimationEndListener realEndListener =
+ mEndListenerForProperty.get(property);
+
+ setEndListenerForProperty((animation, canceled, value, velocity) -> {
+ if (realEndListener != null) {
+ realEndListener.onAnimationEnd(animation, canceled, value, velocity);
+ }
+
+ listener.onAnimationEnd(animation, canceled, value, velocity);
+ }, property);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
new file mode 100644
index 000000000000..db819d57417b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.animation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.PointF;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+
+ @Spy
+ private TestableStackController mStackController = new TestableStackController();
+
+ private int mStackOffset;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ addOneMoreThanRenderLimitBubbles();
+ mLayout.setController(mStackController);
+ mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
+ }
+
+ /**
+ * Test moving around the stack, and make sure the position is updated correctly, and the stack
+ * direction is correct.
+ */
+ @Test
+ public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException {
+ mStackController.moveFirstBubbleWithStackFollowing(200, 100);
+
+ // The first bubble should have moved instantly, the rest should be waiting for animation.
+ assertEquals(200, mViews.get(0).getTranslationX(), .1f);
+ assertEquals(100, mViews.get(0).getTranslationY(), .1f);
+ assertEquals(0, mViews.get(1).getTranslationX(), .1f);
+ assertEquals(0, mViews.get(1).getTranslationY(), .1f);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Make sure the rest of the stack got moved to the right place and is stacked to the left.
+ testStackedAtPosition(200, 100, -1);
+ assertEquals(new PointF(200, 100), mStackController.getStackPosition());
+
+ mStackController.moveFirstBubbleWithStackFollowing(1000, 500);
+
+ // The first bubble again should have moved instantly while the rest remained where they
+ // were until the animation takes over.
+ assertEquals(1000, mViews.get(0).getTranslationX(), .1f);
+ assertEquals(500, mViews.get(0).getTranslationY(), .1f);
+ assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f);
+ assertEquals(100, mViews.get(1).getTranslationY(), .1f);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Make sure the rest of the stack moved again, including the first bubble not moving, and
+ // is stacked to the right now that we're on the right side of the screen.
+ testStackedAtPosition(1000, 500, 1);
+ assertEquals(new PointF(1000, 500), mStackController.getStackPosition());
+ }
+
+ @Test
+ @Ignore("Sporadically failing due to DynamicAnimation not settling.")
+ public void testFlingSideways() throws InterruptedException {
+ // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
+ // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
+ // but should bounce back down.
+ mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_X,
+ 5000f, 1.15f, new SpringForce(), mWidth * 1f);
+ mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_Y,
+ 0f, 1.15f, new SpringForce(), 0f);
+
+ // Nothing should move initially since the animations haven't begun, including the first
+ // view.
+ assertEquals(0f, mViews.get(0).getTranslationX(), 1f);
+ assertEquals(0f, mViews.get(0).getTranslationY(), 1f);
+
+ // Wait for the flinging.
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y);
+
+ // Wait for the springing.
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y);
+
+ // Once the dust has settled, we should have flung all the way to the right side, with the
+ // stack stacked off to the right now.
+ testStackedAtPosition(mWidth * 1f, 0f, 1);
+ }
+
+ @Test
+ @Ignore("Sporadically failing due to DynamicAnimation not settling.")
+ public void testFlingUpFromBelowBottomCenter() throws InterruptedException {
+ // Move to the center of the screen, just past the bottom.
+ mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100);
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
+ // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
+ // but should bounce back down.
+ mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_X,
+ 0, 1.15f, new SpringForce(), 27f);
+ mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_Y,
+ 5000f, 1.15f, new SpringForce(), 27f);
+
+ // Nothing should move initially since the animations haven't begun.
+ assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f);
+ assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y);
+
+ // Once the dust has settled, we should have flung a bit but then sprung to the final
+ // destination which is (27, 27).
+ testStackedAtPosition(27, 27, -1);
+ }
+
+ @Test
+ public void testChildAdded() throws InterruptedException {
+ // Move the stack to y = 500.
+ mStackController.moveFirstBubbleWithStackFollowing(0f, 500f);
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y);
+
+ final View newView = new FrameLayout(mContext);
+ mLayout.addView(
+ newView,
+ 0,
+ new FrameLayout.LayoutParams(50, 50));
+
+ waitForPropertyAnimations(
+ DynamicAnimation.TRANSLATION_X,
+ DynamicAnimation.TRANSLATION_Y,
+ DynamicAnimation.SCALE_X,
+ DynamicAnimation.SCALE_Y);
+
+ // The new view should be at the top of the stack, in the correct position.
+ assertEquals(0f, newView.getTranslationX(), .1f);
+ assertEquals(500f, newView.getTranslationY(), .1f);
+ assertEquals(1f, newView.getScaleX(), .1f);
+ assertEquals(1f, newView.getScaleY(), .1f);
+ assertEquals(1f, newView.getAlpha(), .1f);
+ }
+
+ @Test
+ public void testChildRemoved() throws InterruptedException {
+ final View firstView = mLayout.getChildAt(0);
+ mLayout.removeView(firstView);
+
+ // The view should still be there, since the controller is animating it out and hasn't yet
+ // actually removed it from the parent view.
+ assertEquals(0, mLayout.indexOfChild(firstView));
+
+ waitForPropertyAnimations(DynamicAnimation.ALPHA);
+ waitForLayoutMessageQueue();
+
+ assertEquals(-1, mLayout.indexOfChild(firstView));
+
+ // The subsequent view should have been translated over to 0, not stacked off to the left.
+ assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+ }
+
+ /**
+ * Checks every child view to make sure it's stacked at the given coordinates, off to the left
+ * or right side depending on offset multiplier.
+ */
+ private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+ // Make sure the rest of the stack moved again, including the first bubble not moving, and
+ // is stacked to the right now that we're on the right side of the screen.
+ for (int i = 0; i < mLayout.getChildCount(); i++) {
+ assertEquals(x + i * offsetMultiplier * mStackOffset,
+ mViews.get(i).getTranslationX(), 2f);
+ assertEquals(y, mViews.get(i).getTranslationY(), 2f);
+ }
+ }
+
+ /**
+ * Testable version of the stack controller that dispatches its animations on the main thread.
+ */
+ private class TestableStackController extends StackAnimationController {
+ @Override
+ public void flingThenSpringFirstBubbleWithStackFollowing(
+ DynamicAnimation.ViewProperty property, float vel, float friction,
+ SpringForce spring, Float finalPosition) {
+ mMainThreadHandler.post(() ->
+ super.flingThenSpringFirstBubbleWithStackFollowing(
+ property, vel, friction, spring, finalPosition));
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index e6d7ee7407d4..98bf3c2743a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -45,9 +45,12 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -73,6 +76,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
private lateinit var userManager: UserManager
@Captor
private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
+ @Captor
+ private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
private lateinit var testableLooper: TestableLooper
private lateinit var privacyItemController: PrivacyItemController
@@ -95,12 +100,12 @@ class PrivacyItemControllerTest : SysuiTestCase() {
}
})).`when`(userManager).getProfiles(anyInt())
- privacyItemController = PrivacyItemController(mContext, callback)
+ privacyItemController = PrivacyItemController(mContext)
}
@Test
- fun testSetListeningTrue() {
- privacyItemController.setListening(true)
+ fun testSetListeningTrueByAddingCallback() {
+ privacyItemController.addCallback(callback)
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
any(AppOpsController.Callback::class.java))
testableLooper.processAllMessages()
@@ -108,6 +113,13 @@ class PrivacyItemControllerTest : SysuiTestCase() {
}
@Test
+ fun testSetListeningTrue() {
+ privacyItemController.setListening(true)
+ verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
+ any(AppOpsController.Callback::class.java))
+ }
+
+ @Test
fun testSetListeningFalse() {
privacyItemController.setListening(true)
privacyItemController.setListening(false)
@@ -121,7 +133,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
.`when`(appOpsController).getActiveAppOpsForUser(anyInt())
- privacyItemController.setListening(true)
+ privacyItemController.addCallback(callback)
testableLooper.processAllMessages()
verify(callback).privacyChanged(capture(argCaptor))
assertEquals(1, argCaptor.value.size)
@@ -131,7 +143,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
fun testSystemApps() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME,
0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
- privacyItemController.setListening(true)
+ privacyItemController.addCallback(callback)
testableLooper.processAllMessages()
verify(callback).privacyChanged(capture(argCaptor))
assertEquals(1, argCaptor.value.size)
@@ -142,8 +154,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
@Test
fun testRegisterReceiver_allUsers() {
val spiedContext = spy(mContext)
- val itemController = PrivacyItemController(spiedContext, callback)
-
+ val itemController = PrivacyItemController(spiedContext)
+ itemController.setListening(true)
verify(spiedContext, atLeastOnce()).registerReceiverAsUser(
eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null),
eq(null))
@@ -170,4 +182,54 @@ class PrivacyItemControllerTest : SysuiTestCase() {
Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED))
verify(userManager).getProfiles(anyInt())
}
+
+ @Test
+ fun testAddMultipleCallbacks() {
+ val otherCallback = mock(PrivacyItemController.Callback::class.java)
+ privacyItemController.addCallback(callback)
+ testableLooper.processAllMessages()
+ verify(callback).privacyChanged(anyList())
+
+ privacyItemController.addCallback(otherCallback)
+ testableLooper.processAllMessages()
+ verify(otherCallback).privacyChanged(anyList())
+ // Adding a callback should not unnecessarily call previous ones
+ verifyNoMoreInteractions(callback)
+ }
+
+ @Test
+ fun testMultipleCallbacksAreUpdated() {
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+ val otherCallback = mock(PrivacyItemController.Callback::class.java)
+ privacyItemController.addCallback(callback)
+ privacyItemController.addCallback(otherCallback)
+ testableLooper.processAllMessages()
+ reset(callback)
+ reset(otherCallback)
+
+ verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
+ argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+ testableLooper.processAllMessages()
+ verify(callback).privacyChanged(anyList())
+ verify(otherCallback).privacyChanged(anyList())
+ }
+
+ @Test
+ fun testRemoveCallback() {
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ val otherCallback = mock(PrivacyItemController.Callback::class.java)
+ privacyItemController.addCallback(callback)
+ privacyItemController.addCallback(otherCallback)
+ testableLooper.processAllMessages()
+ reset(callback)
+ reset(otherCallback)
+
+ verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
+ privacyItemController.removeCallback(callback)
+ argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+ testableLooper.processAllMessages()
+ verify(callback, never()).privacyChanged(anyList())
+ verify(otherCallback).privacyChanged(anyList())
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 40d2da9a7729..fdc9e0c4d7e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -354,7 +354,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(false),
eq(true) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
- eq(0));
+ eq(0),
+ eq(false) /* wasShownHighPriority */);
}
@Test
@@ -382,16 +383,18 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(false),
eq(false) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
- eq(0));
+ eq(0),
+ eq(false) /* wasShownHighPriority */);
}
@Test
- public void testInitializeNotificationInfoView_importance() throws Exception {
+ public void testInitializeNotificationInfoView_highPriority() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
row.setBlockingHelperShowing(true);
row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
row.getEntry().importance = IMPORTANCE_DEFAULT;
+ row.getEntry().setIsHighPriority(true);
when(row.getIsNonblockable()).thenReturn(false);
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
@@ -411,7 +414,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(false),
eq(true) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
- eq(IMPORTANCE_DEFAULT));
+ eq(IMPORTANCE_DEFAULT),
+ eq(true) /* wasShownHighPriority */);
}
@Test
@@ -440,7 +444,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(false),
eq(false) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
- eq(0));
+ eq(0),
+ eq(false) /* wasShownHighPriority */);
}
@Test
@@ -468,7 +473,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
eq(false),
eq(true) /* isForBlockingHelper */,
eq(true) /* isUserSentimentNegative */,
- eq(0));
+ eq(0),
+ eq(false) /* wasShownHighPriority */);
}
@Test
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 f6791dd5778c..08955e3c4d5a 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
@@ -210,7 +210,7 @@ public class NotificationInfoTest extends SysuiTestCase {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView textView = mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -223,7 +223,7 @@ public class NotificationInfoTest extends SysuiTestCase {
.thenReturn(iconDrawable);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final ImageView iconView = mNotificationInfo.findViewById(R.id.pkgicon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -232,7 +232,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_noDelegate() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
@@ -251,7 +251,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Other"));
@@ -263,7 +263,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(GONE, groupNameView.getVisibility());
final TextView groupDividerView = mNotificationInfo.findViewById(R.id.pkg_group_divider);
@@ -280,7 +280,7 @@ public class NotificationInfoTest extends SysuiTestCase {
.thenReturn(notificationChannelGroup);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(View.VISIBLE, groupNameView.getVisibility());
assertEquals("Test Group Name", groupNameView.getText());
@@ -292,7 +292,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -301,7 +301,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_DefaultChannelDoesNotUseChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, true);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, textView.getVisibility());
}
@@ -314,7 +314,7 @@ public class NotificationInfoTest extends SysuiTestCase {
eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, true,
- false, IMPORTANCE_DEFAULT);
+ false, IMPORTANCE_DEFAULT, true);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -323,7 +323,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(VISIBLE, textView.getVisibility());
}
@@ -332,7 +332,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_BlockButton() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final View block = mNotificationInfo.findViewById(R.id.int_block);
final View minimize = mNotificationInfo.findViewById(R.id.block_or_minimize);
assertEquals(VISIBLE, block.getVisibility());
@@ -343,7 +343,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_BlockButton_BlockHelper() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT);
+ true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT, true);
final View block = mNotificationInfo.findViewById(R.id.block);
final View interruptivenessSettings = mNotificationInfo.findViewById(
R.id.interruptiveness_settings);
@@ -356,7 +356,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
assertEquals(VISIBLE, silent.getVisibility());
assertEquals(
@@ -368,7 +368,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_LOW);
+ IMPORTANCE_LOW, false);
final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
assertEquals(VISIBLE, silent.getVisibility());
assertEquals(
@@ -381,7 +381,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_LOW);
+ IMPORTANCE_LOW, false);
final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
assertEquals(VISIBLE, alert.getVisibility());
assertEquals(
@@ -393,7 +393,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
assertEquals(VISIBLE, alert.getVisibility());
assertEquals(
@@ -405,7 +405,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
assertEquals(VISIBLE, silent.getVisibility());
@@ -421,7 +421,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_LOW);
+ IMPORTANCE_LOW, false);
final TextView silent = mNotificationInfo.findViewById(R.id.int_silent);
final TextView alert = mNotificationInfo.findViewById(R.id.int_alert);
assertEquals(VISIBLE, silent.getVisibility());
@@ -437,7 +437,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final View block = mNotificationInfo.findViewById(R.id.block);
final View interruptivenessSettings = mNotificationInfo.findViewById(
R.id.interruptiveness_settings);
@@ -455,7 +455,7 @@ public class NotificationInfoTest extends SysuiTestCase {
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
latch.countDown();
- }, null, true, false, IMPORTANCE_DEFAULT);
+ }, null, true, false, IMPORTANCE_DEFAULT, true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -467,7 +467,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -479,7 +479,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
- }, null, false, false, IMPORTANCE_DEFAULT);
+ }, null, false, false, IMPORTANCE_DEFAULT, true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -488,11 +488,11 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_SettingsButtonReappearsAfterSecondBind() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null,
(View v, NotificationChannel c, int appUid) -> {
- }, null, true, false, IMPORTANCE_DEFAULT);
+ }, null, true, false, IMPORTANCE_DEFAULT, true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertEquals(View.VISIBLE, settingsButton.getVisibility());
}
@@ -501,7 +501,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testLogBlockingHelperCounter_logGutsViewDisplayed() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
verify(mMetricsLogger).write(argThat(logMaker ->
logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
@@ -513,7 +513,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testLogBlockingHelperCounter_logsForBlockingHelper() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, true,
- true, true, IMPORTANCE_DEFAULT);
+ true, true, IMPORTANCE_DEFAULT, true);
mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1));
}
@@ -526,7 +526,7 @@ public class NotificationInfoTest extends SysuiTestCase {
(View v, NotificationChannel c, int appUid) -> {
assertEquals(null, c);
latch.countDown();
- }, null, true, true, IMPORTANCE_DEFAULT);
+ }, null, true, true, IMPORTANCE_DEFAULT, true);
mNotificationInfo.findViewById(R.id.info).performClick();
// Verify that listener was triggered.
@@ -539,7 +539,7 @@ public class NotificationInfoTest extends SysuiTestCase {
throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
- null, true, true, IMPORTANCE_DEFAULT);
+ null, true, true, IMPORTANCE_DEFAULT, true);
final TextView channelNameView =
mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(GONE, channelNameView.getVisibility());
@@ -550,7 +550,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
- null, true, true, IMPORTANCE_DEFAULT);
+ null, true, true, IMPORTANCE_DEFAULT, true);
final TextView blockView = mNotificationInfo.findViewById(R.id.block);
assertEquals(GONE, blockView.getVisibility());
}
@@ -559,7 +559,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testbindNotification_BlockingHelper() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, false, false,
- true, true, IMPORTANCE_DEFAULT);
+ true, true, IMPORTANCE_DEFAULT, true);
final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
@@ -569,7 +569,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -580,7 +580,7 @@ public class NotificationInfoTest extends SysuiTestCase {
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mTestableLooper.processAllMessages();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), eq(TEST_UID), any());
@@ -591,7 +591,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
mTestableLooper.processAllMessages();
@@ -605,7 +605,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.minimize).performClick();
mTestableLooper.processAllMessages();
@@ -619,7 +619,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.findViewById(R.id.int_silent).performClick();
mTestableLooper.processAllMessages();
@@ -633,7 +633,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_alert).performClick();
mTestableLooper.processAllMessages();
@@ -647,7 +647,7 @@ public class NotificationInfoTest extends SysuiTestCase {
int originalImportance = mNotificationChannel.getImportance();
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.handleCloseControls(true, false);
mTestableLooper.processAllMessages();
@@ -662,7 +662,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.handleCloseControls(true, false);
@@ -680,7 +680,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
- true, false /* isNonblockable */, IMPORTANCE_DEFAULT
+ true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false
);
mNotificationInfo.findViewById(R.id.int_block).performClick();
@@ -702,7 +702,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */,
- true, false /* isNonblockable */, IMPORTANCE_DEFAULT
+ true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false
);
mNotificationInfo.findViewById(R.id.int_block).performClick();
@@ -724,7 +724,7 @@ public class NotificationInfoTest extends SysuiTestCase {
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
true /* provisioned */,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT);
+ true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
NotificationGuts guts = spy(new NotificationGuts(mContext, null));
when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -752,7 +752,7 @@ public class NotificationInfoTest extends SysuiTestCase {
10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
null /* onSettingsClick */, null /* onAppSettingsClick */ , true /* provisioned */,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT);
+ true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
NotificationGuts guts = spy(new NotificationGuts(mContext, null));
when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -781,7 +781,7 @@ public class NotificationInfoTest extends SysuiTestCase {
null /* onSettingsClick */, null /* onAppSettingsClick */ ,
false /* isNonblockable */, true /* isForBlockingHelper */,
true, true /* isUserSentimentNegative */, /* isNoisy */
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.handleCloseControls(true /* save */, false /* force */);
@@ -800,7 +800,7 @@ public class NotificationInfoTest extends SysuiTestCase {
null /* onSettingsClick */, null /* onAppSettingsClick */,
true /* provisioned */,
false /* isNonblockable */, true /* isForBlockingHelper */,
- true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT);
+ true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
mNotificationInfo.findViewById(R.id.block).performClick();
mTestableLooper.processAllMessages();
@@ -823,7 +823,7 @@ public class NotificationInfoTest extends SysuiTestCase {
true /* isForBlockingHelper */,
true,
false /* isUserSentimentNegative */,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
NotificationGuts guts = mock(NotificationGuts.class);
doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
mNotificationInfo.setGutsParent(guts);
@@ -838,7 +838,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -852,7 +852,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
waitForUndoButton();
@@ -888,7 +888,8 @@ public class NotificationInfoTest extends SysuiTestCase {
false /* isNonblockable */,
true /* isForBlockingHelper */,
true /* isUserSentimentNegative */,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT,
+ false);
mNotificationInfo.findViewById(R.id.block).performClick();
waitForUndoButton();
@@ -913,7 +914,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -928,7 +929,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -949,7 +950,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.handleCloseControls(true, false);
@@ -967,7 +968,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
waitForUndoButton();
@@ -988,7 +989,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -1006,7 +1007,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.findViewById(R.id.int_silent).performClick();
waitForUndoButton();
@@ -1027,7 +1028,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_alert).performClick();
waitForUndoButton();
@@ -1049,7 +1050,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.findViewById(R.id.int_silent).performClick();
waitForUndoButton();
@@ -1071,7 +1072,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_UNSPECIFIED);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_LOW);
+ IMPORTANCE_LOW, false);
mNotificationInfo.findViewById(R.id.int_alert).performClick();
waitForUndoButton();
@@ -1092,7 +1093,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -1108,7 +1109,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
waitForUndoButton();
@@ -1125,7 +1126,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
(Runnable saveImportance, StatusBarNotification sbn) -> {
- }, null, null, true, true, IMPORTANCE_DEFAULT);
+ }, null, null, true, true, IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
mTestableLooper.processAllMessages();
@@ -1143,7 +1144,7 @@ public class NotificationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
(Runnable saveImportance, StatusBarNotification sbn) -> {
saveImportance.run();
- }, null, null, true, false, IMPORTANCE_DEFAULT
+ }, null, null, true, false, IMPORTANCE_DEFAULT, false
);
mNotificationInfo.findViewById(R.id.int_block).performClick();
@@ -1170,7 +1171,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.minimize).performClick();
waitForUndoButton();
@@ -1183,7 +1184,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
waitForUndoButton();
@@ -1196,7 +1197,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, true);
mNotificationInfo.findViewById(R.id.int_silent).performClick();
waitForUndoButton();
@@ -1210,7 +1211,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_alert).performClick();
waitForUndoButton();
@@ -1224,7 +1225,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
waitForUndoButton();
@@ -1236,7 +1237,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationChannel.setImportance(IMPORTANCE_LOW);
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_DEFAULT, false);
mNotificationInfo.findViewById(R.id.int_block).performClick();
waitForUndoButton();
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 736f3840b91a..ae70b01cd35c 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
@@ -352,7 +352,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
RETURNS_DEEP_STUBS);
String key = Integer.toString(i);
when(row.getStatusBarNotification().getKey()).thenReturn(key);
- when(mNotificationData.isHighPriority(row.getStatusBarNotification())).thenReturn(true);
+ when(row.getEntry().isHighPriority()).thenReturn(true);
when(mStackScroller.getChildAt(i)).thenReturn(row);
}
@@ -368,8 +368,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
RETURNS_DEEP_STUBS);
String key = Integer.toString(i);
when(row.getStatusBarNotification().getKey()).thenReturn(key);
- when(mNotificationData.isHighPriority(row.getStatusBarNotification()))
- .thenReturn(false);
+ when(row.getEntry().isHighPriority()).thenReturn(false);
when(mStackScroller.getChildAt(i)).thenReturn(row);
}
@@ -385,8 +384,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
RETURNS_DEEP_STUBS);
String key = Integer.toString(i);
when(row.getStatusBarNotification().getKey()).thenReturn(key);
- when(mNotificationData.isHighPriority(row.getStatusBarNotification()))
- .thenReturn(i < 3);
+ when(row.getEntry().isHighPriority()).thenReturn(i < 3);
when(mStackScroller.getChildAt(i)).thenReturn(row);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index c0f7f0ce217f..1ded835e9651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -85,7 +85,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
return;
}
mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
- ColorDisplayController.AUTO_MODE_TWILIGHT);
+ ColorDisplayManager.AUTO_MODE_TWILIGHT);
verify(mQsTileHost).addTile("night");
}
@@ -95,7 +95,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
return;
}
mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
- ColorDisplayController.AUTO_MODE_CUSTOM);
+ ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
verify(mQsTileHost).addTile("night");
}
@@ -105,7 +105,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
return;
}
mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
- ColorDisplayController.AUTO_MODE_DISABLED);
+ ColorDisplayManager.AUTO_MODE_DISABLED);
verify(mQsTileHost, never()).addTile("night");
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index b7b95ef3ff6a..3b98f0ca8ce2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -51,6 +51,8 @@ public class NotificationPanelViewTest extends SysuiTestCase {
private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock
private KeyguardStatusView mKeyguardStatusView;
+ @Mock
+ private KeyguardStatusBarView mKeyguardStatusBar;
private NotificationPanelView mNotificationPanelView;
@Before
@@ -93,6 +95,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
super(NotificationPanelViewTest.this.mContext, null);
mNotificationStackScroller = mNotificationStackScrollLayout;
mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
+ mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
}
}
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index efa4e79cc318..73fcb0150a9e 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6780,22 +6780,22 @@ message MetricsEvent {
CONVERSATION_ACTIONS = 1615;
// ACTION: Actions from a text classifier are shown to user.
- // CATEGORY: CONVERSATION_ACTIONS
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
// OS: Q
ACTION_TEXT_CLASSIFIER_ACTIONS_SHOWN = 1616;
- // ACTION: Event time of a text classifier event in unix timestamp.
- // CATEGORY: CONVERSATION_ACTIONS, LANGUAGE_DETECTION
+ // FIELD: Event time of a text classifier event in unix timestamp.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
// OS: Q
FIELD_TEXT_CLASSIFIER_EVENT_TIME = 1617;
// ACTION: Users compose their own replies instead of using suggested ones.
- // CATEGORY: CONVERSATION_ACTIONS
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
// OS: Q
ACTION_TEXT_CLASSIFIER_MANUAL_REPLY = 1618;
// ACTION: Text classifier generates an action.
- // CATEGORY: CONVERSATION_ACTIONS
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
// OS: Q
ACTION_TEXT_CLASSIFIER_ACTIONS_GENERATED = 1619;
@@ -6840,7 +6840,7 @@ message MetricsEvent {
// OPEN: Settings > Display > Adaptive sleep
// OS: Q
SETTINGS_ADAPTIVE_SLEEP = 1628;
-
+
// Tagged data for SMART_REPLY_VISIBLE and NOTIFICATION_ITEM_ACTION.
// The UI location of the notification containing the smart suggestions.
// This is a NotificationLocation object (see the NotificationLocation
@@ -6862,6 +6862,48 @@ message MetricsEvent {
// OS: Q
FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
+ // OPEN: Settings > System > Aware
+ // OS: Q
+ SETTINGS_AWARE = 1632;
+
+ // OPEN: Settings > System > Aware > Disable > Dialog
+ // OS: Q
+ DIALOG_AWARE_DISABLE = 1633;
+
+ // FIELD: Session ID of TextClassifierEvent.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_SESSION_ID = 1634;
+
+ // FIELD: First entity type.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_FIRST_ENTITY_TYPE = 1635;
+ // FIELD: Second entity type.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_SECOND_ENTITY_TYPE = 1636;
+
+ // FIELD: Third entity type.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_THIRD_ENTITY_TYPE = 1637;
+
+ // FIELD: Score of the suggestion.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_SCORE = 1638;
+
+ // FIELD: widget type, e.g: notification, textview
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_WIDGET_TYPE = 1639;
+
+ // FIELD: version of the widget.
+ // CATEGORY: LANGUAGE_DETECTION, CONVERSATION_ACTIONS
+ // OS: Q
+ FIELD_TEXT_CLASSIFIER_WIDGET_VERSION = 1640;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f128d86784a0..3a8931630338 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -241,6 +241,8 @@ message SystemMessage {
NOTE_NETWORK_LOST_INTERNET = 742;
// The system default network switched to a different network
NOTE_NETWORK_SWITCH = 743;
+ // Device logged-in captive portal network successfully
+ NOTE_NETWORK_LOGGED_IN = 744;
// Notify the user that their work profile has been deleted
// Package: android
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 665691994f44..c063e82766ed 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -491,6 +491,9 @@ message WifiLog {
// List of PNO scan stats, one element for each mobility state
repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
+
+ // Wifi p2p statistics
+ optional WifiP2pStats wifi_p2p_stats = 129;
}
// Information that gets logged for every WiFi connection.
@@ -962,6 +965,10 @@ message StaEvent {
// NetworkAgent Wifi usability score of connected wifi
optional int32 last_wifi_usability_score = 15 [default = -1];
+
+ // Prediction horizon (in second) of Wifi usability score provided by external
+ // system app
+ optional int32 last_prediction_horizon_sec = 16 [default = -1];
}
// Wi-Fi Aware metrics
@@ -1679,6 +1686,10 @@ message WifiIsUnusableEvent {
// NetworkAgent wifi usability score of connected wifi.
// Defaults to -1 if the score was never set.
optional int32 last_wifi_usability_score = 11 [default = -1];
+
+ // Prediction horizon (in second) of Wifi usability score provided by external
+ // system app
+ optional int32 last_prediction_horizon_sec = 12 [default = -1];
}
message PasspointProfileTypeCount {
@@ -1811,6 +1822,10 @@ message WifiUsabilityStatsEntry {
// The total number of beacons received from the last radio chip reset
optional int64 total_beacon_rx = 22;
+
+ // Prediction horizon (in second) of Wifi usability score provided by external
+ // system app
+ optional int32 prediction_horizon_sec = 23;
}
message WifiUsabilityStats {
@@ -1860,3 +1875,135 @@ message DeviceMobilityStatePnoScanStats {
// the total duration elapsed while in this mobility state with PNO scans running, in ms
optional int64 pno_duration_ms = 4;
}
+
+// The information about the Wifi P2p events.
+message WifiP2pStats {
+
+ // Group event list tracking sessions and client counts in tethered mode.
+ repeated GroupEvent group_event = 1;
+
+ // Session information that gets logged for every Wifi P2p connection.
+ repeated P2pConnectionEvent connection_event = 2;
+
+ // Number of persistent group in the user profile.
+ optional int32 num_persistent_group = 3;
+
+ // Number of peer scan.
+ optional int32 num_total_peer_scans = 4;
+
+ // Number of service scan.
+ optional int32 num_total_service_scans = 5;
+}
+
+message P2pConnectionEvent {
+
+ enum ConnectionType {
+
+ // fresh new connection.
+ CONNECTION_FRESH = 0;
+
+ // reinvoke a group.
+ CONNECTION_REINVOKE = 1;
+
+ // create a group with the current device as the group owner locally.
+ CONNECTION_LOCAL = 2;
+
+ // create a group or join a group with config.
+ CONNECTION_FAST = 3;
+ }
+
+ enum ConnectivityLevelFailure {
+
+ // Failure is unknown.
+ CLF_UNKNOWN = 0;
+
+ // No failure.
+ CLF_NONE = 1;
+
+ // Timeout for current connecting request.
+ CLF_TIMEOUT = 2;
+
+ // The connecting request is canceled by the user.
+ CLF_CANCEL = 3;
+
+ // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer.
+ CLF_PROV_DISC_FAIL = 4;
+
+ // Invitation failure, e.g. rejected by the peer.
+ CLF_INVITATION_FAIL = 5;
+
+ // Incoming request is rejected by the user.
+ CLF_USER_REJECT = 6;
+
+ // New connection request is issued before ending previous connecting request.
+ CLF_NEW_CONNECTION_ATTEMPT = 7;
+ }
+
+ // WPS method.
+ enum WpsMethod {
+ // WPS is skipped for Group Reinvoke.
+ WPS_NA = -1;
+
+ // Push button configuration.
+ WPS_PBC = 0;
+
+ // Display pin method configuration - pin is generated and displayed on device.
+ WPS_DISPLAY = 1;
+
+ // Keypad pin method configuration - pin is entered on device.
+ WPS_KEYPAD = 2;
+
+ // Label pin method configuration - pin is labelled on device.
+ WPS_LABEL = 3;
+ }
+
+ // Start time of the connection.
+ optional int64 start_time_millis = 1;
+
+ // Type of the connection.
+ optional ConnectionType connection_type = 2;
+
+ // WPS method.
+ optional WpsMethod wps_method = 3 [default = WPS_NA];
+
+ // Duration to connect.
+ optional int32 duration_taken_to_connect_millis = 4;
+
+ // Failures that happen at the connectivity layer.
+ optional ConnectivityLevelFailure connectivity_level_failure_code = 5;
+}
+
+// GroupEvent tracking group information from GroupStarted to GroupRemoved.
+message GroupEvent {
+
+ enum GroupRole {
+
+ GROUP_OWNER = 0;
+
+ GROUP_CLIENT = 1;
+ }
+
+ // The ID of network in supplicant for this group.
+ optional int32 net_id = 1;
+
+ // Start time of the group.
+ optional int64 start_time_millis = 2;
+
+ // Channel frequency used for Group.
+ optional int32 channel_frequency = 3;
+
+ // Is group owner or group client.
+ optional GroupRole group_role = 5;
+
+ // Number of connected clients.
+ optional int32 num_connected_clients = 6;
+
+ // Cumulative number of connected clients.
+ optional int32 num_cumulative_clients = 7;
+
+ // The session duration.
+ optional int32 session_duration_millis = 8;
+
+ // The idle duration. A group without any client is in idle.
+ optional int32 idle_duration_millis = 9;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index bcff4e0a90f1..303230b00c6f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -402,7 +402,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
MagnificationGestureHandler magnificationGestureHandler =
new MagnificationGestureHandler(mContext,
mAms.getMagnificationController(),
- detectControlGestures, triggerable);
+ detectControlGestures, triggerable, displayId);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 49db488bc740..2fbaee65864a 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -43,7 +43,6 @@ import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
-import android.view.Display;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -149,6 +148,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
private PointerCoords[] mTempPointerCoords;
private PointerProperties[] mTempPointerProperties;
+ private final int mDisplayId;
+
private final Queue<MotionEvent> mDebugInputEventHistory;
private final Queue<MotionEvent> mDebugOutputEventHistory;
@@ -162,11 +163,13 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
* @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some
* external shortcut invoking {@link #notifyShortcutTriggered},
* {@code false} if it should ignore such triggers.
+ * @param displayId The logical display id.
*/
public MagnificationGestureHandler(Context context,
MagnificationController magnificationController,
boolean detectTripleTap,
- boolean detectShortcutTrigger) {
+ boolean detectShortcutTrigger,
+ int displayId) {
if (DEBUG_ALL) {
Log.i(LOG_TAG,
"MagnificationGestureHandler(detectTripleTap = " + detectTripleTap
@@ -174,6 +177,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
}
mMagnificationController = magnificationController;
+ mDisplayId = displayId;
mDelegatingState = new DelegatingState();
mDetectingState = new DetectingState(context);
@@ -259,8 +263,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
void notifyShortcutTriggered() {
if (mDetectShortcutTrigger) {
- // TODO: multi-display support for magnification gesture handler
- boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY,
+ boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
/* animate */ true);
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
@@ -422,8 +425,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
- // TODO: multi-display support for magnification gesture handler
- mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX,
+ mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* event consumed: */ true;
}
@@ -440,8 +442,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
return mScaling;
}
- // TODO: multi-display support for magnification gesture handler
- final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY);
+ final float initialScale = mMagnificationController.getScale(mDisplayId);
final float targetScale = initialScale * detector.getScaleFactor();
// Don't allow a gesture to move the user further outside the
@@ -463,8 +464,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
final float pivotX = detector.getFocusX();
final float pivotY = detector.getFocusY();
if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
- // TODO: multi-display support for magnification gesture handler
- mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false,
+ mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
return /* handled: */ true;
}
@@ -524,10 +524,9 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
}
final float eventX = event.getX();
final float eventY = event.getY();
- // TODO: multi-display support for magnification gesture handler
if (mMagnificationController.magnificationRegionContains(
- Display.DEFAULT_DISPLAY, eventX, eventY)) {
- mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY,
+ mDisplayId, eventX, eventY)) {
+ mMagnificationController.setCenter(mDisplayId, eventX, eventY,
/* animate */ mLastMoveOutsideMagnifiedRegion,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
mLastMoveOutsideMagnifiedRegion = false;
@@ -665,9 +664,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
+ mDisplayId, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -684,8 +682,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
// If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
// to ensure reachability of
// STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
- // TODO: multi-display support for magnification gesture handler
- || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+ || mMagnificationController.isMagnifying(mDisplayId)) {
afterMultiTapTimeoutTransitionToDelegatingState();
@@ -697,8 +694,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
}
break;
case ACTION_POINTER_DOWN: {
- // TODO: multi-display support for magnification gesture handler
- if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+ if (mMagnificationController.isMagnifying(mDisplayId)) {
transitionTo(mPanningScalingState);
clear();
} else {
@@ -727,9 +723,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
- // TODO: multi-display support for magnification gesture handler
if (!mMagnificationController.magnificationRegionContains(
- Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
+ mDisplayId, event.getX(), event.getY())) {
transitionToDelegatingStateAndClear();
@@ -880,8 +875,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
clear();
// Toggle zoom
- // TODO: multi-display support for magnification gesture handler
- if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+ if (mMagnificationController.isMagnifying(mDisplayId)) {
zoomOff();
} else {
zoomOn(up.getX(), up.getY());
@@ -893,9 +887,8 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
clear();
- // TODO: multi-display support for magnification gesture handler
mViewportDraggingState.mZoomedInBeforeDrag =
- mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY);
+ mMagnificationController.isMagnifying(mDisplayId);
zoomOn(down.getX(), down.getY());
@@ -922,8 +915,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
mShortcutTriggered = state;
- // TODO: multi-display support for magnification gesture handler
- mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
+ mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
}
/**
@@ -958,8 +950,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
final float scale = MathUtils.constrain(
mMagnificationController.getPersistedScale(),
MIN_SCALE, MAX_SCALE);
- // TODO: multi-display support for magnification gesture handler
- mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY,
+ mMagnificationController.setScaleAndCenter(mDisplayId,
scale, centerX, centerY,
/* animate */ true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -967,8 +958,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
private void zoomOff() {
if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
- // TODO: multi-display support for magnification gesture handler
- mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true);
+ mMagnificationController.reset(mDisplayId, /* animate */ true);
}
private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
@@ -990,6 +980,7 @@ class MagnificationGestureHandler extends BaseEventStreamTransformation {
", mCurrentState=" + State.nameOf(mCurrentState) +
", mPreviousState=" + State.nameOf(mPreviousState) +
", mMagnificationController=" + mMagnificationController +
+ ", mDisplayId=" + mDisplayId +
'}';
}
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 8ffaddefd3ef..65e31f3acf14 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -435,7 +435,7 @@ class TouchExplorer extends BaseEventStreamTransformation
MotionEvent click_event = MotionEvent.obtain(event.getDownTime(),
event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0,
- event.getSource(), event.getFlags());
+ event.getSource(), event.getDisplayId(), event.getFlags());
final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus);
click_event.recycle();
@@ -1029,7 +1029,7 @@ class TouchExplorer extends BaseEventStreamTransformation
event.getEventTime(), event.getAction(), event.getPointerCount(),
props, coords, event.getMetaState(), event.getButtonState(),
1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
- event.getSource(), event.getFlags());
+ event.getSource(), event.getDisplayId(), event.getFlags());
}
/**
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 4f58d7920d74..303734a4043c 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -18,6 +18,7 @@ package com.android.server.backup;
import static com.android.server.backup.BackupManagerService.TAG;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
@@ -41,9 +42,10 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.UserManager;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import java.io.File;
@@ -75,19 +77,25 @@ import java.io.PrintWriter;
* system user is unlocked before any other users.
*/
public class Trampoline extends IBackupManager.Stub {
- // When this file is present, the backup service is inactive.
+ /**
+ * Name of file that disables the backup service. If this file exists, then backup is disabled
+ * for all users.
+ */
private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
+ /**
+ * Name of file for non-system users that enables the backup service for the user. Backup is
+ * disabled by default in non-system users.
+ */
+ private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";
+
// Product-level suppression of backup/restore.
private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
private static final String BACKUP_THREAD = "backup";
- /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */
- private static final int MULTI_USER_DISABLED = 0;
- private static final int MULTI_USER_ENABLED = 1;
-
private final Context mContext;
+ private final UserManager mUserManager;
private final boolean mGlobalDisable;
// Lock to write backup suppress files.
@@ -104,20 +112,13 @@ public class Trampoline extends IBackupManager.Stub {
mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mUserManager = UserManager.get(context);
}
protected boolean isBackupDisabled() {
return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
}
- private boolean isMultiUserEnabled() {
- return Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.BACKUP_MULTI_USER_ENABLED,
- MULTI_USER_DISABLED)
- == MULTI_USER_ENABLED;
- }
-
protected int binderGetCallingUserId() {
return Binder.getCallingUserHandle().getIdentifier();
}
@@ -126,21 +127,65 @@ public class Trampoline extends IBackupManager.Stub {
return Binder.getCallingUid();
}
- protected File getSuppressFileForUser(int userId) {
- return new File(UserBackupManagerFiles.getBaseStateDir(userId),
+ /** Stored in the system user's directory. */
+ protected File getSuppressFileForSystemUser() {
+ return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
BACKUP_SUPPRESS_FILENAME);
}
- protected void createBackupSuppressFileForUser(int userId) throws IOException {
- synchronized (mStateLock) {
- getSuppressFileForUser(userId).getParentFile().mkdirs();
- getSuppressFileForUser(userId).createNewFile();
+ /** Stored in the system user's directory and the file is indexed by the user it refers to. */
+ protected File getActivatedFileForNonSystemUser(int userId) {
+ return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
+ BACKUP_ACTIVATED_FILENAME + "-" + userId);
+ }
+
+ private void createFile(File file) throws IOException {
+ if (file.exists()) {
+ return;
+ }
+
+ file.getParentFile().mkdirs();
+ if (!file.createNewFile()) {
+ Slog.w(TAG, "Failed to create file " + file.getPath());
}
}
- private void deleteBackupSuppressFileForUser(int userId) {
- if (!getSuppressFileForUser(userId).delete()) {
- Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId);
+ private void deleteFile(File file) {
+ if (!file.exists()) {
+ return;
+ }
+
+ if (!file.delete()) {
+ Slog.w(TAG, "Failed to delete file " + file.getPath());
+ }
+ }
+
+ /**
+ * Deactivates the backup service for user {@code userId}. If this is the system user, it
+ * creates a suppress file which disables backup for all users. If this is a non-system user, it
+ * only deactivates backup for that user by deleting its activate file.
+ */
+ @GuardedBy("mStateLock")
+ private void deactivateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ createFile(getSuppressFileForSystemUser());
+ } else {
+ deleteFile(getActivatedFileForNonSystemUser(userId));
+ }
+ }
+
+ /**
+ * Enables the backup service for user {@code userId}. If this is the system user, it deletes
+ * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
+ * deleting the suppress file does not automatically enable backup for non-system users, they
+ * need their own activate file in order to participate in the service.
+ */
+ @GuardedBy("mStateLock")
+ private void activateBackupForUserLocked(int userId) throws IOException {
+ if (userId == UserHandle.USER_SYSTEM) {
+ deleteFile(getSuppressFileForSystemUser());
+ } else {
+ createFile(getActivatedFileForNonSystemUser(userId));
}
}
@@ -148,24 +193,31 @@ public class Trampoline extends IBackupManager.Stub {
// admin (device owner or profile owner).
private boolean isUserReadyForBackup(int userId) {
return mService != null && mService.getServiceUsers().get(userId) != null
- && !isBackupSuppressedForUser(userId);
+ && isBackupActivatedForUser(userId);
}
- private boolean isBackupSuppressedForUser(int userId) {
- // If backup is disabled for system user, it's disabled for all other users on device.
- if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) {
- return true;
- }
- if (userId != UserHandle.USER_SYSTEM) {
- return getSuppressFileForUser(userId).exists();
+ /**
+ * Backup is activated for the system user if the suppress file does not exist. Backup is
+ * activated for non-system users if the suppress file does not exist AND the user's activated
+ * file exists.
+ */
+ private boolean isBackupActivatedForUser(int userId) {
+ if (getSuppressFileForSystemUser().exists()) {
+ return false;
}
- return false;
+
+ return userId == UserHandle.USER_SYSTEM
+ || getActivatedFileForNonSystemUser(userId).exists();
}
protected Context getContext() {
return mContext;
}
+ protected UserManager getUserManager() {
+ return mUserManager;
+ }
+
protected BackupManagerService createBackupManagerService() {
return new BackupManagerService(mContext, this, mHandlerThread);
}
@@ -198,23 +250,17 @@ public class Trampoline extends IBackupManager.Stub {
/**
* Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
- * Starts the backup service for this user if it's the system user or if the service supports
- * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time
- * low.
+ * Starts the backup service for this user if backup is active for this user. Offloads work onto
+ * the handler thread {@link #mHandlerThread} to keep unlock time low.
*/
void unlockUser(int userId) {
- if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
- Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId);
- return;
- }
-
postToHandler(() -> startServiceForUser(userId));
}
private void startServiceForUser(int userId) {
// We know that the user is unlocked here because it is called from setBackupServiceActive
// and unlockUser which have these guarantees. So we can check if the file exists.
- if (mService != null && !isBackupSuppressedForUser(userId)) {
+ if (mService != null && isBackupActivatedForUser(userId)) {
Slog.i(TAG, "Starting service for user: " + userId);
mService.startServiceForUser(userId);
}
@@ -225,11 +271,6 @@ public class Trampoline extends IBackupManager.Stub {
* Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
*/
void stopUser(int userId) {
- if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
- Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId);
- return;
- }
-
postToHandler(
() -> {
if (mService != null) {
@@ -240,45 +281,63 @@ public class Trampoline extends IBackupManager.Stub {
}
/**
- * Only privileged callers should be changing the backup state. This method only acts on {@link
- * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the
- * system user also deactivates backup in all users.
- *
- * This call will only work if the calling {@code userID} is unlocked.
+ * The system user and managed profiles can only be acted on by callers in the system or root
+ * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
+ * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
*/
- public void setBackupServiceActive(int userId, boolean makeActive) {
- int caller = binderGetCallingUid();
- if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
- throw new SecurityException("No permission to configure backup activity");
+ private void enforcePermissionsOnUser(int userId) throws SecurityException {
+ boolean isRestrictedUser =
+ userId == UserHandle.USER_SYSTEM
+ || getUserManager().getUserInfo(userId).isManagedProfile();
+
+ if (isRestrictedUser) {
+ int caller = binderGetCallingUid();
+ if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
+ throw new SecurityException("No permission to configure backup activity");
+ }
+ } else {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.BACKUP, "No permission to configure backup activity");
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to configure backup activity");
}
+ }
+
+ /**
+ * Only privileged callers should be changing the backup state. Deactivating backup in the
+ * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
+ * is unlocked at this point yet, so handle both cases.
+ */
+ public void setBackupServiceActive(int userId, boolean makeActive) {
+ enforcePermissionsOnUser(userId);
if (mGlobalDisable) {
Slog.i(TAG, "Backup service not supported");
return;
}
- if (userId != UserHandle.USER_SYSTEM) {
- Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId);
- return;
- }
-
- if (makeActive == isBackupServiceActive(userId)) {
- Slog.i(TAG, "No change in backup service activity");
- return;
- }
-
synchronized (mStateLock) {
Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active");
if (makeActive) {
if (mService == null) {
mService = createBackupManagerService();
}
- deleteBackupSuppressFileForUser(userId);
- startServiceForUser(userId);
+ try {
+ activateBackupForUserLocked(userId);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to persist backup service activity");
+ }
+
+ // If the user is unlocked, we can start the backup service for it. Otherwise we
+ // will start the service when the user is unlocked as part of its unlock callback.
+ if (getUserManager().isUserUnlocked(userId)) {
+ startServiceForUser(userId);
+ }
} else {
try {
//TODO(b/121198006): what if this throws an exception?
- createBackupSuppressFileForUser(userId);
+ deactivateBackupForUserLocked(userId);
} catch (IOException e) {
Slog.e(TAG, "Unable to persist backup service inactivity");
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fda7279c45d1..14e235489b97 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -98,10 +98,10 @@ import android.net.VpnService;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetdService;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.shared.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -238,6 +238,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+ // How long to dismiss network notification.
+ private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
+
// Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -474,6 +477,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
/**
+ * This event can handle dismissing notification by given network id.
+ */
+ public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -507,7 +515,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes.
- private final ProxyTracker mProxyTracker;
+ @VisibleForTesting
+ protected final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
@@ -816,7 +825,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mPolicyManagerInternal = checkNotNull(
LocalServices.getService(NetworkPolicyManagerInternal.class),
"missing NetworkPolicyManagerInternal");
- mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED);
+ mProxyTracker = makeProxyTracker();
mNetd = NetdService.getInstance();
mKeyStore = KeyStore.getInstance();
@@ -982,6 +991,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
deps);
}
+ @VisibleForTesting
+ protected ProxyTracker makeProxyTracker() {
+ return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+ }
+
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -2477,6 +2491,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
+ if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
+ && valid) {
+ nai.captivePortalLoginNotified = true;
+ showNetworkNotification(nai, NotificationType.LOGGED_IN);
+ }
final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
@@ -2497,7 +2516,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateCapabilities(oldScore, nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
- if (valid) handleFreshlyValidatedNetwork(nai);
+ if (valid) {
+ handleFreshlyValidatedNetwork(nai);
+ // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
+ // valid.
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.NO_INTERNET);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.LOST_INTERNET);
+ }
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
@@ -2521,6 +2548,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
final int oldScore = nai.getCurrentScore();
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
+ if (visible) {
+ nai.captivePortalLoginNotified = false;
+ }
if (nai.lastCaptivePortalDetected &&
Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2532,7 +2562,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateCapabilities(oldScore, nai, nai.networkCapabilities);
}
if (!visible) {
- mNotifier.clearNotification(netId);
+ // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
+ // notifications belong to the same network may be cleared unexpected.
+ mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
+ mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
} else {
if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -3238,9 +3271,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
pw.decreaseIndent();
}
- private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+ private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
final String action;
switch (type) {
+ case LOGGED_IN:
+ action = Settings.ACTION_WIFI_SETTINGS;
+ mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
+ nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+ break;
case NO_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
break;
@@ -3253,10 +3292,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
Intent intent = new Intent(action);
- intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.settings",
- "com.android.settings.wifi.WifiNoInternetDialog");
+ if (type != NotificationType.LOGGED_IN) {
+ intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+ }
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
@@ -3274,7 +3315,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
!nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
return;
}
- showValidationNotification(nai, NotificationType.NO_INTERNET);
+ showNetworkNotification(nai, NotificationType.NO_INTERNET);
}
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3283,7 +3324,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
- showValidationNotification(nai, NotificationType.LOST_INTERNET);
+ showNetworkNotification(nai, NotificationType.LOST_INTERNET);
}
}
@@ -3429,6 +3470,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
case EVENT_DATA_SAVER_CHANGED:
handleRestrictBackgroundChanged(toBool(msg.arg1));
break;
+ case EVENT_TIMEOUT_NOTIFICATION:
+ mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+ break;
}
}
}
@@ -3686,20 +3730,46 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ /**
+ * Returns information about the proxy a certain network is using. If given a null network, it
+ * it will return the proxy for the bound network for the caller app or the default proxy if
+ * none.
+ *
+ * @param network the network we want to get the proxy information for.
+ * @return Proxy information if a network has a proxy configured, or otherwise null.
+ */
@Override
public ProxyInfo getProxyForNetwork(Network network) {
- if (network == null) return mProxyTracker.getDefaultProxy();
final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
if (globalProxy != null) return globalProxy;
- if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
- // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
- // caller may not have.
+ if (network == null) {
+ // Get the network associated with the calling UID.
+ final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(),
+ true);
+ if (activeNetwork == null) {
+ return null;
+ }
+ return getLinkPropertiesProxyInfo(activeNetwork);
+ } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) {
+ // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+ // caller may not have.
+ return getLinkPropertiesProxyInfo(network);
+ }
+ // No proxy info available if the calling UID does not have network access.
+ return null;
+ }
+
+ @VisibleForTesting
+ protected boolean queryUserAccess(int uid, int netId) {
+ return NetworkUtils.queryUserAccess(uid, netId);
+ }
+
+ private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null;
synchronized (nai) {
- final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
- if (proxyInfo == null) return null;
- return new ProxyInfo(proxyInfo);
+ final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
+ return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
}
}
@@ -3723,11 +3793,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
mProxyTracker.setDefaultProxy(proxy);
}
- // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy.
- // This method gets called when any network changes proxy, but the broadcast only ever contains
- // the default proxy (even if it hasn't changed).
- // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network
- // world where an app might be bound to a non-default network.
+ // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
+ // when any network changes proxy.
+ // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
+ // multi-network world where an app might be bound to a non-default network.
private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
@@ -5894,12 +5963,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
scheduleUnvalidatedPrompt(networkAgent);
- if (networkAgent.isVPN()) {
- // Temporarily disable the default proxy (not global).
- mProxyTracker.setDefaultProxyEnabled(false);
- // TODO: support proxy per network.
- }
-
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
// capabilities, so it only needs to be done once on initial connect, not every time the
@@ -5918,10 +5981,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
- mProxyTracker.setDefaultProxyEnabled(true);
updateUids(networkAgent, networkAgent.networkCapabilities, null);
}
disconnectAndDestroyNetwork(networkAgent);
+ if (networkAgent.isVPN()) {
+ // As the active or bound network changes for apps, broadcast the default proxy, as
+ // apps may need to update their proxy data. This is called after disconnecting from
+ // VPN to make sure we do not broadcast the old proxy data.
+ // TODO(b/122649188): send the broadcast only to VPN users.
+ mProxyTracker.sendProxyBroadcast();
+ }
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
// going into or coming out of SUSPEND: re-score and notify
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 89ff33810aa2..c064453360c5 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,9 +46,7 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.InetAddresses;
import android.net.INetd;
-import android.net.INetdUnsolicitedEventListener;
import android.net.INetworkManagementEventObserver;
import android.net.ITetheringStatsProvider;
import android.net.InterfaceConfiguration;
@@ -63,6 +61,7 @@ import android.net.RouteInfo;
import android.net.TetherStatsParcel;
import android.net.UidRange;
import android.net.shared.NetdService;
+import android.net.shared.NetworkObserverRegistry;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
@@ -207,16 +206,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub
private INetd mNetdService;
- private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener;
+ private NMSNetworkObserverRegistry mNetworkObserverRegistry;
private IBatteryStats mBatteryStats;
private final Thread mThread;
private CountDownLatch mConnectedSignal = new CountDownLatch(1);
- private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
- new RemoteCallbackList<>();
-
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
@GuardedBy("mTetheringStatsProviders")
@@ -326,8 +322,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mDaemonHandler = new Handler(FgThread.get().getLooper());
- mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener();
-
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
@@ -346,7 +340,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mFgHandler = null;
mThread = null;
mServices = null;
- mNetdUnsolicitedEventListener = null;
+ mNetworkObserverRegistry = null;
}
static NetworkManagementService create(Context context, String socket, SystemServices services)
@@ -394,14 +388,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub
@Override
public void registerObserver(INetworkManagementEventObserver observer) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- mObservers.register(observer);
+ mNetworkObserverRegistry.registerObserver(observer);
}
@Override
public void unregisterObserver(INetworkManagementEventObserver observer) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- mObservers.unregister(observer);
+ mNetworkObserverRegistry.unregisterObserver(observer);
}
@FunctionalInterface
@@ -409,127 +401,101 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
}
- private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
- final int length = mObservers.beginBroadcast();
- try {
- for (int i = 0; i < length; i++) {
- try {
- eventCallback.sendCallback(mObservers.getBroadcastItem(i));
- } catch (RemoteException | RuntimeException e) {
- }
- }
- } finally {
- mObservers.finishBroadcast();
+ private class NMSNetworkObserverRegistry extends NetworkObserverRegistry {
+ NMSNetworkObserverRegistry(Context context, Handler handler, INetd netd)
+ throws RemoteException {
+ super(context, handler, netd);
}
- }
-
- /**
- * Notify our observers of an interface status change
- */
- private void notifyInterfaceStatusChanged(String iface, boolean up) {
- invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
- }
-
- /**
- * Notify our observers of an interface link state change
- * (typically, an Ethernet cable has been plugged-in or unplugged).
- */
- private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
- invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
- }
-
- /**
- * Notify our observers of an interface addition.
- */
- private void notifyInterfaceAdded(String iface) {
- invokeForAllObservers(o -> o.interfaceAdded(iface));
- }
-
- /**
- * Notify our observers of an interface removal.
- */
- private void notifyInterfaceRemoved(String iface) {
- // netd already clears out quota and alerts for removed ifaces; update
- // our sanity-checking state.
- mActiveAlerts.remove(iface);
- mActiveQuotas.remove(iface);
- invokeForAllObservers(o -> o.interfaceRemoved(iface));
- }
-
- /**
- * Notify our observers of a limit reached.
- */
- private void notifyLimitReached(String limitName, String iface) {
- invokeForAllObservers(o -> o.limitReached(limitName, iface));
- }
- /**
- * Notify our observers of a change in the data activity state of the interface
- */
- private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos,
- int uid, boolean fromRadio) {
- final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
- if (isMobile) {
- if (!fromRadio) {
- if (mMobileActivityFromRadio) {
- // If this call is not coming from a report from the radio itself, but we
- // have previously received reports from the radio, then we will take the
- // power state to just be whatever the radio last reported.
- powerState = mLastPowerStateFromRadio;
+ /**
+ * Notify our observers of a change in the data activity state of the interface
+ */
+ @Override
+ public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+ int uid, boolean fromRadio) {
+ final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
+ int powerState = isActive
+ ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+ : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+
+ if (isMobile) {
+ if (!fromRadio) {
+ if (mMobileActivityFromRadio) {
+ // If this call is not coming from a report from the radio itself, but we
+ // have previously received reports from the radio, then we will take the
+ // power state to just be whatever the radio last reported.
+ powerState = mLastPowerStateFromRadio;
+ }
+ } else {
+ mMobileActivityFromRadio = true;
}
- } else {
- mMobileActivityFromRadio = true;
- }
- if (mLastPowerStateFromRadio != powerState) {
- mLastPowerStateFromRadio = powerState;
- try {
- getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
- } catch (RemoteException e) {
+ if (mLastPowerStateFromRadio != powerState) {
+ mLastPowerStateFromRadio = powerState;
+ try {
+ getBatteryStats().noteMobileRadioPowerState(powerState, tsNanos, uid);
+ } catch (RemoteException e) {
+ }
}
StatsLog.write_non_chained(StatsLog.MOBILE_RADIO_POWER_STATE_CHANGED, uid, null,
powerState);
}
- }
- if (ConnectivityManager.isNetworkTypeWifi(type)) {
- if (mLastPowerStateFromWifi != powerState) {
- mLastPowerStateFromWifi = powerState;
- try {
- getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
- } catch (RemoteException e) {
+ if (ConnectivityManager.isNetworkTypeWifi(type)) {
+ if (mLastPowerStateFromWifi != powerState) {
+ mLastPowerStateFromWifi = powerState;
+ try {
+ getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos, uid);
+ } catch (RemoteException e) {
+ }
}
StatsLog.write_non_chained(StatsLog.WIFI_RADIO_POWER_STATE_CHANGED, uid, null,
powerState);
}
- }
- boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
- || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
-
- if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
- // Report the change in data activity. We don't do this if this is a change
- // on the mobile network, that is not coming from the radio itself, and we
- // have previously seen change reports from the radio. In that case only
- // the radio is the authority for the current state.
- final boolean active = isActive;
- invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
- Integer.toString(type), active, tsNanos));
- }
+ if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
+ // Report the change in data activity. We don't do this if this is a change
+ // on the mobile network, that is not coming from the radio itself, and we
+ // have previously seen change reports from the radio. In that case only
+ // the radio is the authority for the current state.
+ final boolean active = isActive;
+ super.notifyInterfaceClassActivity(type, isActive, tsNanos, uid, fromRadio);
+ }
- boolean report = false;
- synchronized (mIdleTimerLock) {
- if (mActiveIdleTimers.isEmpty()) {
- // If there are no idle timers, we are not monitoring activity, so we
- // are always considered active.
- isActive = true;
+ boolean report = false;
+ synchronized (mIdleTimerLock) {
+ if (mActiveIdleTimers.isEmpty()) {
+ // If there are no idle timers, we are not monitoring activity, so we
+ // are always considered active.
+ isActive = true;
+ }
+ if (mNetworkActive != isActive) {
+ mNetworkActive = isActive;
+ report = isActive;
+ }
}
- if (mNetworkActive != isActive) {
- mNetworkActive = isActive;
- report = isActive;
+ if (report) {
+ reportNetworkActive();
}
}
- if (report) {
- reportNetworkActive();
+
+ /**
+ * Notify our observers of an interface removal.
+ */
+ @Override
+ public void notifyInterfaceRemoved(String iface) {
+ // netd already clears out quota and alerts for removed ifaces; update
+ // our sanity-checking state.
+ mActiveAlerts.remove(iface);
+ mActiveQuotas.remove(iface);
+ super.notifyInterfaceRemoved(iface);
+ }
+
+ @Override
+ public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+ // Don't need to post to mDaemonHandler because the only thing
+ // that notifyCleartextNetwork does is post to a handler
+ ActivityManager.getService().notifyCleartextNetwork(uid,
+ HexDump.hexStringToByteArray(hex));
}
}
@@ -558,7 +524,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub
return;
}
// No current code examines the interface parameter in a global alert. Just pass null.
- mDaemonHandler.post(() -> notifyLimitReached(LIMIT_GLOBAL_ALERT, null));
+ mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyLimitReached(
+ LIMIT_GLOBAL_ALERT, null));
}
}
@@ -590,10 +557,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
private void connectNativeNetdService() {
mNetdService = mServices.getNetd();
try {
- mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener);
- if (DBG) Slog.d(TAG, "Register unsolicited event listener");
+ mNetworkObserverRegistry = new NMSNetworkObserverRegistry(
+ mContext, mDaemonHandler, mNetdService);
+ if (DBG) Slog.d(TAG, "Registered NetworkObserverRegistry");
} catch (RemoteException | ServiceSpecificException e) {
- Slog.e(TAG, "Failed to set Netd unsolicited event listener " + e);
+ Slog.wtf(TAG, "Failed to register NetworkObserverRegistry: " + e);
}
}
@@ -697,120 +665,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
- /**
- * Notify our observers of a new or updated interface address.
- */
- private void notifyAddressUpdated(String iface, LinkAddress address) {
- invokeForAllObservers(o -> o.addressUpdated(iface, address));
- }
-
- /**
- * Notify our observers of a deleted interface address.
- */
- private void notifyAddressRemoved(String iface, LinkAddress address) {
- invokeForAllObservers(o -> o.addressRemoved(iface, address));
- }
-
- /**
- * Notify our observers of DNS server information received.
- */
- private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
- invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
- }
-
- /**
- * Notify our observers of a route change.
- */
- private void notifyRouteChange(boolean updated, RouteInfo route) {
- if (updated) {
- invokeForAllObservers(o -> o.routeUpdated(route));
- } else {
- invokeForAllObservers(o -> o.routeRemoved(route));
- }
- }
-
- private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
- @Override
- public void onInterfaceClassActivityChanged(boolean isActive,
- int label, long timestamp, int uid) throws RemoteException {
- final long timestampNanos;
- if (timestamp <= 0) {
- timestampNanos = SystemClock.elapsedRealtimeNanos();
- } else {
- timestampNanos = timestamp;
- }
- mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
- isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
- : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
- timestampNanos, uid, false));
- }
-
- @Override
- public void onQuotaLimitReached(String alertName, String ifName)
- throws RemoteException {
- mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
- }
-
- @Override
- public void onInterfaceDnsServerInfo(String ifName,
- long lifetime, String[] servers) throws RemoteException {
- mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
- }
-
- @Override
- public void onInterfaceAddressUpdated(String addr,
- String ifName, int flags, int scope) throws RemoteException {
- final LinkAddress address = new LinkAddress(addr, flags, scope);
- mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
- }
-
- @Override
- public void onInterfaceAddressRemoved(String addr,
- String ifName, int flags, int scope) throws RemoteException {
- final LinkAddress address = new LinkAddress(addr, flags, scope);
- mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
- }
-
- @Override
- public void onInterfaceAdded(String ifName) throws RemoteException {
- mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
- }
-
- @Override
- public void onInterfaceRemoved(String ifName) throws RemoteException {
- mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
- }
-
- @Override
- public void onInterfaceChanged(String ifName, boolean up)
- throws RemoteException {
- mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
- }
-
- @Override
- public void onInterfaceLinkStateChanged(String ifName, boolean up)
- throws RemoteException {
- mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
- }
-
- @Override
- public void onRouteChanged(boolean updated,
- String route, String gateway, String ifName) throws RemoteException {
- final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
- ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
- ifName);
- mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
- }
-
- @Override
- public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
- // Don't need to post to mDaemonHandler because the only thing
- // that notifyCleartextNetwork does is post to a handler
- ActivityManager.getService().notifyCleartextNetwork(uid,
- HexDump.hexStringToByteArray(hex));
- }
- }
-
//
// Netd Callback handling
//
@@ -859,16 +713,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub
throw new IllegalStateException(errorMessage);
}
if (cooked[2].equals("added")) {
- notifyInterfaceAdded(cooked[3]);
+ mNetworkObserverRegistry.notifyInterfaceAdded(cooked[3]);
return true;
} else if (cooked[2].equals("removed")) {
- notifyInterfaceRemoved(cooked[3]);
+ mNetworkObserverRegistry.notifyInterfaceRemoved(cooked[3]);
return true;
} else if (cooked[2].equals("changed") && cooked.length == 5) {
- notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
+ mNetworkObserverRegistry.notifyInterfaceStatusChanged(
+ cooked[3], cooked[4].equals("up"));
return true;
} else if (cooked[2].equals("linkstate") && cooked.length == 5) {
- notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
+ mNetworkObserverRegistry.notifyInterfaceLinkStateChanged(
+ cooked[3], cooked[4].equals("up"));
return true;
}
throw new IllegalStateException(errorMessage);
@@ -882,7 +738,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
throw new IllegalStateException(errorMessage);
}
if (cooked[2].equals("alert")) {
- notifyLimitReached(cooked[3], cooked[4]);
+ mNetworkObserverRegistry.notifyLimitReached(cooked[3], cooked[4]);
return true;
}
throw new IllegalStateException(errorMessage);
@@ -908,9 +764,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub
timestampNanos = SystemClock.elapsedRealtimeNanos();
}
boolean isActive = cooked[2].equals("active");
- notifyInterfaceClassActivity(Integer.parseInt(cooked[3]),
- isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
- : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+ mNetworkObserverRegistry.notifyInterfaceClassActivity(
+ Integer.parseInt(cooked[3]), isActive,
timestampNanos, processUid, false);
return true;
// break;
@@ -937,9 +792,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
if (cooked[2].equals("updated")) {
- notifyAddressUpdated(iface, address);
+ mNetworkObserverRegistry.notifyAddressUpdated(iface, address);
} else {
- notifyAddressRemoved(iface, address);
+ mNetworkObserverRegistry.notifyAddressRemoved(iface, address);
}
return true;
// break;
@@ -959,7 +814,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub
throw new IllegalStateException(errorMessage);
}
String[] servers = cooked[5].split(",");
- notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers);
+ mNetworkObserverRegistry.notifyInterfaceDnsServerInfo(
+ cooked[3], lifetime, servers);
}
return true;
// break;
@@ -998,7 +854,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub
InetAddress gateway = null;
if (via != null) gateway = InetAddress.parseNumericAddress(via);
RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
- notifyRouteChange(cooked[2].equals("updated"), route);
+ mNetworkObserverRegistry.notifyRouteChange(
+ cooked[2].equals("updated"), route);
return true;
} catch (IllegalArgumentException e) {}
}
@@ -1461,9 +1318,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub
if (ConnectivityManager.isNetworkTypeMobile(type)) {
mNetworkActive = false;
}
- mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
- DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
- SystemClock.elapsedRealtimeNanos(), -1, false));
+ mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+ type, true /* isActive */, SystemClock.elapsedRealtimeNanos(), -1, false));
}
}
@@ -1486,9 +1342,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
throw new IllegalStateException(e);
}
mActiveIdleTimers.remove(iface);
- mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
- DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
- SystemClock.elapsedRealtimeNanos(), -1, false));
+ mDaemonHandler.post(() -> mNetworkObserverRegistry.notifyInterfaceClassActivity(
+ params.type, false /* isActive */, SystemClock.elapsedRealtimeNanos(), -1,
+ false));
}
}
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index d09823efb6fa..a5a515f93e75 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -34,11 +34,11 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.Preconditions;
@@ -48,6 +48,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
/**
* Find the best Service, and bind to it.
@@ -61,16 +64,20 @@ public class ServiceWatcher implements ServiceConnection {
public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+ /** Function to run on binder interface. */
+ public interface BinderRunner {
+ /** Called to run client code with the binder. */
+ void run(IBinder binder) throws RemoteException;
+ }
+
/**
- * The runner that runs on the binder retrieved from {@link ServiceWatcher}.
+ * Function to run on binder interface.
+ * @param <T> Type to return.
*/
- public interface BinderRunner {
- /**
- * Runs on the retrieved binder.
- *
- * @param binder the binder retrieved from the {@link ServiceWatcher}.
- */
- void run(IBinder binder);
+ public interface BlockingBinderRunner<T> {
+ /** Called to run client code with the binder. */
+ T run(IBinder binder) throws RemoteException;
}
public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
@@ -120,18 +127,14 @@ public class ServiceWatcher implements ServiceConnection {
private final Handler mHandler;
- // this lock is held to ensure the service binder is not exposed (via runOnBinder) until after
- // the new service initialization work has completed
- private final Object mBindLock = new Object();
-
// read/write from handler thread
+ private IBinder mBestService;
private int mCurrentUserId;
// read from any thread, write from handler thread
private volatile ComponentName mBestComponent;
private volatile int mBestVersion;
private volatile int mBestUserId;
- private volatile IBinder mBestService;
public ServiceWatcher(Context context, String logTag, String action,
int overlaySwitchResId, int defaultServicePackageNameResId,
@@ -163,17 +166,9 @@ public class ServiceWatcher implements ServiceConnection {
mBestService = null;
}
- // called on handler thread
- @GuardedBy("mBindLock")
- protected void onBind() {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- }
+ protected void onBind() {}
- // called on handler thread
- @GuardedBy("mBindLock")
- protected void onUnbind() {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- }
+ protected void onUnbind() {}
/**
* Start this watcher, including binding to the current best match and
@@ -248,25 +243,6 @@ public class ServiceWatcher implements ServiceConnection {
return bestComponent == null ? null : bestComponent.getPackageName();
}
- /**
- * Runs the given BinderRunner if currently connected. All invocations to runOnBinder are run
- * serially.
- */
- public final void runOnBinder(BinderRunner runner) {
- synchronized (mBindLock) {
- IBinder service = mBestService;
- if (service != null) {
- try {
- runner.run(service);
- } catch (Exception e) {
- // remote exceptions cannot be allowed to crash system server
- Log.e(TAG, "exception while while running " + runner + " on " + service
- + " from " + this, e);
- }
- }
- }
- }
-
private boolean isServiceMissing() {
return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction),
PackageManager.MATCH_DIRECT_BOOT_AWARE
@@ -380,28 +356,66 @@ public class ServiceWatcher implements ServiceConnection {
mBestUserId = UserHandle.USER_NULL;
}
+ /**
+ * Runs the given function asynchronously if currently connected. Suppresses any RemoteException
+ * thrown during execution.
+ */
+ public final void runOnBinder(BinderRunner runner) {
+ runOnHandler(() -> {
+ if (mBestService == null) {
+ return;
+ }
+
+ try {
+ runner.run(mBestService);
+ } catch (RuntimeException e) {
+ // the code being run is privileged, but may be outside the system server, and thus
+ // we cannot allow runtime exceptions to crash the system server
+ Log.e(TAG, "exception while while running " + runner + " on " + mBestService
+ + " from " + this, e);
+ } catch (RemoteException e) {
+ // do nothing
+ }
+ });
+ }
+
+ /**
+ * Runs the given function synchronously if currently connected, and returns the default value
+ * if not currently connected or if any exception is thrown.
+ */
+ public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) {
+ try {
+ return runOnHandlerBlocking(() -> {
+ if (mBestService == null) {
+ return defaultValue;
+ }
+
+ try {
+ return runner.run(mBestService);
+ } catch (RemoteException e) {
+ return defaultValue;
+ }
+ });
+ } catch (InterruptedException e) {
+ return defaultValue;
+ }
+ }
+
@Override
public final void onServiceConnected(ComponentName component, IBinder binder) {
- mHandler.post(() -> {
+ runOnHandler(() -> {
if (D) Log.d(mTag, component + " connected");
-
- // hold the lock so that mBestService cannot be used by runOnBinder until complete
- synchronized (mBindLock) {
- mBestService = binder;
- onBind();
- }
+ mBestService = binder;
+ onBind();
});
}
@Override
public final void onServiceDisconnected(ComponentName component) {
- mHandler.post(() -> {
+ runOnHandler(() -> {
if (D) Log.d(mTag, component + " disconnected");
-
mBestService = null;
- synchronized (mBindLock) {
- onUnbind();
- }
+ onUnbind();
});
}
@@ -410,4 +424,32 @@ public class ServiceWatcher implements ServiceConnection {
ComponentName bestComponent = mBestComponent;
return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion;
}
+
+ private void runOnHandler(Runnable r) {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ r.run();
+ } else {
+ mHandler.post(r);
+ }
+ }
+
+ private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException {
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ try {
+ return c.call();
+ } catch (Exception e) {
+ // Function cannot throw exception, this should never happen
+ throw new IllegalStateException(e);
+ }
+ } else {
+ FutureTask<T> task = new FutureTask<>(c);
+ mHandler.post(task);
+ try {
+ return task.get();
+ } catch (ExecutionException e) {
+ // Function cannot throw exception, this should never happen
+ throw new IllegalStateException(e);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9d810cd8f3ca..cec825fb9c00 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -112,6 +112,7 @@ import android.os.storage.StorageManagerInternal;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
+import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
import android.sysprop.VoldProperties;
@@ -463,6 +464,7 @@ class StorageManagerService extends IStorageManager.Stub
= { "password", "default", "pattern", "pin" };
private final Context mContext;
+ private final ContentResolver mResolver;
private volatile IVold mVold;
private volatile IStoraged mStoraged;
@@ -797,6 +799,14 @@ class StorageManagerService extends IStorageManager.Stub
refreshIsolatedStorageSettings();
}
});
+ // For now, simply clone property when it changes
+ DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE,
+ mContext.getMainExecutor(), (namespace, name, value) -> {
+ if (DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED.equals(name)) {
+ Settings.Global.putString(mResolver,
+ Settings.Global.ISOLATED_STORAGE_REMOTE, value);
+ }
+ });
refreshIsolatedStorageSettings();
}
@@ -1523,6 +1533,8 @@ class StorageManagerService extends IStorageManager.Stub
SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)));
mContext = context;
+ mResolver = mContext.getContentResolver();
+
mCallbacks = new Callbacks(FgThread.get().getLooper());
mLockPatternUtils = new LockPatternUtils(mContext);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index ea6d435c7ba5..f6e698ff7839 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -33,8 +33,10 @@ import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.ExternalVibration;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IExternalVibratorService;
import android.os.IVibratorService;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
@@ -75,16 +77,18 @@ public class VibratorService extends IVibratorService.Stub
private static final String TAG = "VibratorService";
private static final boolean DEBUG = false;
private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
+ private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
- // Scale levels. Each level is defined as the delta between the current setting and the default
- // intensity for that type of vibration (i.e. current - default).
- private static final int SCALE_VERY_LOW = -2;
- private static final int SCALE_LOW = -1;
- private static final int SCALE_NONE = 0;
- private static final int SCALE_HIGH = 1;
- private static final int SCALE_VERY_HIGH = 2;
+ // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
+ // and the default intensity for that type of vibration (i.e. current - default).
+ private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100
+ private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
+ private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
+ private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
+ private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
+ private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
// Gamma adjustments for scale levels.
private static final float SCALE_VERY_LOW_GAMMA = 2.0f;
@@ -111,6 +115,7 @@ public class VibratorService extends IVibratorService.Stub
private final int mPreviousVibrationsLimit;
private final boolean mAllowPriorityVibrationsInLowPowerMode;
private final boolean mSupportsAmplitudeControl;
+ private final boolean mSupportsExternalControl;
private final int mDefaultVibrationAmplitude;
private final SparseArray<VibrationEffect> mFallbackEffects;
private final SparseArray<Integer> mProcStatesCache = new SparseArray();
@@ -138,18 +143,20 @@ public class VibratorService extends IVibratorService.Stub
@GuardedBy("mLock")
private Vibration mCurrentVibration;
private int mCurVibUid = -1;
+ private ExternalVibration mCurrentExternalVibration;
+ private boolean mVibratorUnderExternalControl;
private boolean mLowPowerMode;
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
- native static boolean vibratorExists();
- native static void vibratorInit();
- native static void vibratorOn(long milliseconds);
- native static void vibratorOff();
- native static boolean vibratorSupportsAmplitudeControl();
- native static void vibratorSetAmplitude(int amplitude);
- native static long vibratorPerformEffect(long effect, long strength);
+ static native boolean vibratorExists();
+ static native void vibratorInit();
+ static native void vibratorOn(long milliseconds);
+ static native void vibratorOff();
+ static native boolean vibratorSupportsAmplitudeControl();
+ static native void vibratorSetAmplitude(int amplitude);
+ static native long vibratorPerformEffect(long effect, long strength);
static native boolean vibratorSupportsExternalControl();
static native void vibratorSetExternalControl(boolean enabled);
@@ -218,6 +225,9 @@ public class VibratorService extends IVibratorService.Stub
}
public boolean isHapticFeedback() {
+ if (VibratorService.this.isHapticFeedback(usageHint)) {
+ return true;
+ }
if (effect instanceof VibrationEffect.Prebaked) {
VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
switch (prebaked.getId()) {
@@ -239,19 +249,11 @@ public class VibratorService extends IVibratorService.Stub
}
public boolean isNotification() {
- switch (usageHint) {
- case AudioAttributes.USAGE_NOTIFICATION:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
- return true;
- default:
- return false;
- }
+ return VibratorService.this.isNotification(usageHint);
}
public boolean isRingtone() {
- return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+ return VibratorService.this.isRingtone(usageHint);
}
public boolean isFromSystem() {
@@ -332,6 +334,7 @@ public class VibratorService extends IVibratorService.Stub
vibratorOff();
mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+ mSupportsExternalControl = vibratorSupportsExternalControl();
mContext = context;
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -379,6 +382,8 @@ public class VibratorService extends IVibratorService.Stub
mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_NONE_GAMMA));
mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_HIGH_GAMMA));
mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_VERY_HIGH_GAMMA));
+
+ ServiceManager.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
}
private VibrationEffect createEffectFromResource(int resId) {
@@ -562,6 +567,16 @@ public class VibratorService extends IVibratorService.Stub
}
}
+
+ // If something has external control of the vibrator, assume that it's more
+ // important for now.
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ }
+ return;
+ }
+
// If the current vibration is repeating and the incoming one is non-repeating,
// then ignore the non-repeating vibration. This is so that we don't cancel
// vibrations that are meant to grab the attention of the user, like ringtones and
@@ -648,6 +663,11 @@ public class VibratorService extends IVibratorService.Stub
mThread.cancel();
mThread = null;
}
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.mute();
+ mCurrentExternalVibration = null;
+ setVibratorUnderExternalControl(false);
+ }
doVibratorOff();
reportFinishVibrationLocked();
} finally {
@@ -1095,6 +1115,26 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private static boolean isNotification(int usageHint) {
+ switch (usageHint) {
+ case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isRingtone(int usageHint) {
+ return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+ }
+
+ private static boolean isHapticFeedback(int usageHint) {
+ return usageHint == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
+ }
+
private void noteVibratorOnLocked(int uid, long millis) {
try {
mBatteryStatsService.noteVibratorOn(uid, millis);
@@ -1116,6 +1156,18 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private void setVibratorUnderExternalControl(boolean externalControl) {
+ if (DEBUG) {
+ if (externalControl) {
+ Slog.d(TAG, "Vibrator going under external control.");
+ } else {
+ Slog.d(TAG, "Taking back control of vibrator.");
+ }
+ }
+ mVibratorUnderExternalControl = externalControl;
+ vibratorSetExternalControl(externalControl);
+ }
+
private class VibrateThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
private final int mUid;
@@ -1290,6 +1342,13 @@ public class VibratorService extends IVibratorService.Stub
} else {
pw.println("null");
}
+ pw.print(" mCurrentExternalVibration=");
+ if (mCurrentExternalVibration != null) {
+ pw.println(mCurrentExternalVibration.toString());
+ } else {
+ pw.println("null");
+ }
+ pw.println(" mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
pw.println(" mLowPowerMode=" + mLowPowerMode);
pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
pw.println(" mNotificationIntensity=" + mNotificationIntensity);
@@ -1310,6 +1369,87 @@ public class VibratorService extends IVibratorService.Stub
new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
}
+ final class ExternalVibratorService extends IExternalVibratorService.Stub {
+ @Override
+ public int onExternalVibrationStart(ExternalVibration vib) {
+ if (!mSupportsExternalControl) {
+ return SCALE_MUTE;
+ }
+ if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+ vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+ != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ + " tried to play externally controlled vibration"
+ + " without VIBRATE permission, ignoring.");
+ return SCALE_MUTE;
+ }
+
+ final int scaleLevel;
+ synchronized (mLock) {
+ if (!vib.equals(mCurrentExternalVibration)) {
+ if (mCurrentExternalVibration == null) {
+ // If we're not under external control right now, then cancel any normal
+ // vibration that may be playing and ready the vibrator for external
+ // control.
+ doCancelVibrateLocked();
+ setVibratorUnderExternalControl(true);
+ }
+ // At this point we either have an externally controlled vibration playing, or
+ // no vibration playing. Since the interface defines that only one externally
+ // controlled vibration can play at a time, by returning something other than
+ // SCALE_MUTE from this function we can be assured that if we are currently
+ // playing vibration, it will be muted in favor of the new vibration.
+ //
+ // Note that this doesn't support multiple concurrent external controls, as we
+ // would need to mute the old one still if it came from a different controller.
+ mCurrentExternalVibration = vib;
+ if (DEBUG) {
+ Slog.e(TAG, "Playing external vibration: " + vib);
+ }
+ }
+ final int usage = vib.getAudioAttributes().getUsage();
+ final int defaultIntensity;
+ final int currentIntensity;
+ if (isRingtone(usage)) {
+ defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
+ currentIntensity = mRingIntensity;
+ } else if (isNotification(usage)) {
+ defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
+ currentIntensity = mNotificationIntensity;
+ } else if (isHapticFeedback(usage)) {
+ defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
+ currentIntensity = mHapticFeedbackIntensity;
+ } else {
+ defaultIntensity = 0;
+ currentIntensity = 0;
+ }
+ scaleLevel = currentIntensity - defaultIntensity;
+ }
+ if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
+ return scaleLevel;
+ } else {
+ // Presumably we want to play this but something about our scaling has gone
+ // wrong, so just play with no scaling.
+ Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level "
+ + scaleLevel + " for vibration " + vib);
+ return SCALE_NONE;
+ }
+ }
+
+ @Override
+ public void onExternalVibrationStop(ExternalVibration vib) {
+ synchronized (mLock) {
+ if (vib.equals(mCurrentExternalVibration)) {
+ mCurrentExternalVibration = null;
+ setVibratorUnderExternalControl(false);
+ if (DEBUG) {
+ Slog.e(TAG, "Stopping external vibration" + vib);
+ }
+ }
+ }
+ }
+ }
+
private final class VibratorShellCommand extends ShellCommand {
private final IBinder mToken;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a96676e5fc5f..5d6c2f074f9d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -677,6 +677,7 @@ public final class ActiveServices {
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName);
intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -1649,6 +1650,7 @@ public final class ActiveServices {
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName);
intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index dd2b33ab1179..d025d739055b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,13 +16,19 @@
package com.android.server.am;
+import static android.provider.DeviceConfig.ActivityManager.KEY_MAX_CACHED_PROCESSES;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
+import android.app.ActivityThread;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertyChangedListener;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -34,7 +40,6 @@ import java.io.PrintWriter;
final class ActivityManagerConstants extends ContentObserver {
// Key names stored in the settings value.
- private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
= "fgservice_min_shown_time";
@@ -69,13 +74,6 @@ final class ActivityManagerConstants extends ContentObserver {
static final String KEY_PROCESS_START_ASYNC = "process_start_async";
static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
- static final String KEY_USE_COMPACTION = "use_compaction";
- static final String KEY_COMPACT_ACTION_1 = "compact_action_1";
- static final String KEY_COMPACT_ACTION_2 = "compact_action_2";
- static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1";
- static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2";
- static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3";
- static final String KEY_COMPACT_THROTTLE_4 = "compact_throttle_4";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -106,13 +104,6 @@ final class ActivityManagerConstants extends ContentObserver {
private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
- private static final boolean DEFAULT_USE_COMPACTION = false;
- public static final int DEFAULT_COMPACT_ACTION_1 = 1;
- public static final int DEFAULT_COMPACT_ACTION_2 = 3;
- public static final long DEFAULT_COMPACT_THROTTLE_1 = 5000;
- public static final long DEFAULT_COMPACT_THROTTLE_2 = 10000;
- public static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
- public static final long DEFAULT_COMPACT_THROTTLE_4 = 10000;
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -232,23 +223,6 @@ final class ActivityManagerConstants extends ContentObserver {
// this long.
public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
- // Use compaction for background apps.
- public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
-
- // Action for compactAppSome.
- public int COMPACT_ACTION_1 = DEFAULT_COMPACT_ACTION_1;
- // Action for compactAppFull;
- public int COMPACT_ACTION_2 = DEFAULT_COMPACT_ACTION_2;
-
- // How long we'll skip second compactAppSome after first compactAppSome
- public long COMPACT_THROTTLE_1 = DEFAULT_COMPACT_THROTTLE_1;
- // How long we'll skip compactAppSome after compactAppFull
- public long COMPACT_THROTTLE_2 = DEFAULT_COMPACT_THROTTLE_2;
- // How long we'll skip compactAppFull after compactAppSome
- public long COMPACT_THROTTLE_3 = DEFAULT_COMPACT_THROTTLE_3;
- // How long we'll skip second compactAppFull after first compactAppFull
- public long COMPACT_THROTTLE_4 = DEFAULT_COMPACT_THROTTLE_4;
-
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -295,10 +269,19 @@ final class ActivityManagerConstants extends ContentObserver {
Settings.Global.getUriFor(
Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED);
+ private final OnPropertyChangedListener mOnDeviceConfigChangedListener =
+ new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(String namespace, String name, String value) {
+ if (KEY_MAX_CACHED_PROCESSES.equals(name)) {
+ updateMaxCachedProcesses();
+ }
+ }
+ };
+
public ActivityManagerConstants(ActivityManagerService service, Handler handler) {
super(handler);
mService = service;
- updateMaxCachedProcesses();
}
public void start(ContentResolver resolver) {
@@ -309,6 +292,11 @@ final class ActivityManagerConstants extends ContentObserver {
updateConstants();
updateActivityStartsLoggingEnabled();
updateBackgroundActivityStartsEnabled();
+ DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ mOnDeviceConfigChangedListener);
+ updateMaxCachedProcesses();
+
}
public void setOverrideMaxCachedProcesses(int value) {
@@ -347,8 +335,6 @@ final class ActivityManagerConstants extends ContentObserver {
// with defaults.
Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
}
- MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,
- DEFAULT_MAX_CACHED_PROCESSES);
BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
DEFAULT_BACKGROUND_SETTLE_TIME);
FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
@@ -406,13 +392,9 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_MEMORY_INFO_THROTTLE_TIME);
TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
DEFAULT_TOP_TO_FGS_GRACE_DURATION);
- USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
- COMPACT_ACTION_1 = mParser.getInt(KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1);
- COMPACT_ACTION_2 = mParser.getInt(KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2);
- COMPACT_THROTTLE_1 = mParser.getLong(KEY_COMPACT_THROTTLE_1, DEFAULT_COMPACT_THROTTLE_1);
- COMPACT_THROTTLE_2 = mParser.getLong(KEY_COMPACT_THROTTLE_2, DEFAULT_COMPACT_THROTTLE_2);
- COMPACT_THROTTLE_3 = mParser.getLong(KEY_COMPACT_THROTTLE_3, DEFAULT_COMPACT_THROTTLE_3);
- COMPACT_THROTTLE_4 = mParser.getLong(KEY_COMPACT_THROTTLE_4, DEFAULT_COMPACT_THROTTLE_4);
+
+ // For new flags that are intended for server-side experiments, please use the new
+ // DeviceConfig package.
updateMaxCachedProcesses();
}
@@ -429,8 +411,19 @@ final class ActivityManagerConstants extends ContentObserver {
}
private void updateMaxCachedProcesses() {
- CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
- ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
+ String maxCachedProcessesFlag = DeviceConfig.getProperty(
+ DeviceConfig.ActivityManager.NAMESPACE, KEY_MAX_CACHED_PROCESSES);
+ try {
+ CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
+ ? (TextUtils.isEmpty(maxCachedProcessesFlag)
+ ? DEFAULT_MAX_CACHED_PROCESSES : Integer.parseInt(maxCachedProcessesFlag))
+ : mOverrideMaxCachedProcesses;
+ } catch (NumberFormatException e) {
+ // Bad flag value from Phenotype, revert to default.
+ Slog.e("ActivityManagerConstants",
+ "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e);
+ CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
+ }
CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
// Note the trim levels do NOT depend on the override process limit, we want
@@ -503,8 +496,6 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println(MEMORY_INFO_THROTTLE_TIME);
pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
pw.println(TOP_TO_FGS_GRACE_DURATION);
- pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("=");
- pw.println(USE_COMPACTION);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b24290f62385..eb643b670fcd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1607,19 +1607,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
} break;
case UPDATE_HTTP_PROXY_MSG: {
- ProxyInfo proxy = (ProxyInfo)msg.obj;
- String host = "";
- String port = "";
- String exclList = "";
- Uri pacFileUrl = Uri.EMPTY;
- if (proxy != null) {
- host = proxy.getHost();
- port = Integer.toString(proxy.getPort());
- exclList = proxy.getExclusionListAsString();
- pacFileUrl = proxy.getPacFileUrl();
- }
synchronized (ActivityManagerService.this) {
- mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl);
+ mProcessList.setAllHttpProxyLocked();
}
} break;
case PROC_START_TIMEOUT_MSG: {
@@ -7228,6 +7217,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mActivityTaskManager.installSystemProviders();
mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
SettingsToPropertiesMapper.start(mContext.getContentResolver());
+ mOomAdjuster.initSettings();
// Now that the settings provider is published we can consider sending
// in a rescue party.
@@ -9340,6 +9330,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized(this) {
mConstants.dump(pw);
+ mOomAdjuster.dumpAppCompactorSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -9739,6 +9730,7 @@ public class ActivityManagerService extends IActivityManager.Stub
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
+ mOomAdjuster.dumpAppCompactorSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
@@ -14656,8 +14648,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
break;
case Proxy.PROXY_CHANGE_ACTION:
- ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
- mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
+ mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
break;
case android.hardware.Camera.ACTION_NEW_PICTURE:
case android.hardware.Camera.ACTION_NEW_VIDEO:
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index fd402cc08c0c..bb55ec3100c0 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -16,34 +16,63 @@
package com.android.server.am;
-import com.android.internal.annotations.GuardedBy;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
+import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
import android.app.ActivityManager;
-
+import android.app.ActivityThread;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
-
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertyChangedListener;
+import android.text.TextUtils;
import android.util.EventLog;
import android.util.StatsLog;
-import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceThread;
import java.io.FileOutputStream;
-import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
public final class AppCompactor {
- /**
- * Processes to compact.
- */
- final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
+ // Phenotype sends int configurations and we map them to the strings we'll use on device,
+ // preventing a weird string value entering the kernel.
+ private static final int COMPACT_ACTION_FILE_FLAG = 1;
+ private static final int COMPACT_ACTION_ANON_FLAG = 2;
+ private static final int COMPACT_ACTION_FULL_FLAG = 3;
+ private static final String COMPACT_ACTION_FILE = "file";
+ private static final String COMPACT_ACTION_ANON = "anon";
+ private static final String COMPACT_ACTION_FULL = "all";
+
+ // Defaults for phenotype flags.
+ @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
+ @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
+ @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
+ @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_4 = 10_000;
+
+ @VisibleForTesting
+ interface PropertyChangedCallbackForTest {
+ void onPropertyChanged();
+ }
+ private PropertyChangedCallbackForTest mTestCallback;
+
+ // Handler constants.
static final int COMPACT_PROCESS_SOME = 1;
static final int COMPACT_PROCESS_FULL = 2;
static final int COMPACT_PROCESS_MSG = 1;
@@ -57,70 +86,106 @@ public final class AppCompactor {
*/
final ServiceThread mCompactionThread;
- final private Handler mCompactionHandler;
-
- final private ActivityManagerService mAm;
- final private ActivityManagerConstants mConstants;
-
- final private String COMPACT_ACTION_FILE = "file";
- final private String COMPACT_ACTION_ANON = "anon";
- final private String COMPACT_ACTION_FULL = "all";
-
- final private String compactActionSome;
- final private String compactActionFull;
-
- final private long throttleSomeSome;
- final private long throttleSomeFull;
- final private long throttleFullSome;
- final private long throttleFullFull;
+ private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
+ new ArrayList<ProcessRecord>();
+ private final ActivityManagerService mAm;
+ private final OnPropertyChangedListener mOnFlagsChangedListener =
+ new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(String namespace, String name, String value) {
+ synchronized (mPhenotypeFlagLock) {
+ if (KEY_USE_COMPACTION.equals(name)) {
+ updateUseCompaction();
+ } else if (KEY_COMPACT_ACTION_1.equals(name)
+ || KEY_COMPACT_ACTION_2.equals(name)) {
+ updateCompactionActions();
+ } else if (KEY_COMPACT_THROTTLE_1.equals(name)
+ || KEY_COMPACT_THROTTLE_2.equals(name)
+ || KEY_COMPACT_THROTTLE_3.equals(name)
+ || KEY_COMPACT_THROTTLE_4.equals(name)) {
+ updateCompactionThrottles();
+ }
+ }
+ if (mTestCallback != null) {
+ mTestCallback.onPropertyChanged();
+ }
+ }
+ };
+
+ private final Object mPhenotypeFlagLock = new Object();
+
+ // Configured by phenotype. Updates from the server take effect immediately.
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting String mCompactActionSome =
+ compactActionIntToString(DEFAULT_COMPACT_ACTION_1);
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting String mCompactActionFull =
+ compactActionIntToString(DEFAULT_COMPACT_ACTION_2);
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+ @GuardedBy("mPhenotypeFlagLock")
+ private boolean mUseCompaction = DEFAULT_USE_COMPACTION;
+
+ // Handler on which compaction runs.
+ private Handler mCompactionHandler;
public AppCompactor(ActivityManagerService am) {
mAm = am;
- mConstants = am.mConstants;
-
mCompactionThread = new ServiceThread("CompactionThread",
THREAD_PRIORITY_FOREGROUND, true);
- mCompactionThread.start();
- mCompactionHandler = new MemCompactionHandler(this);
-
- switch(mConstants.COMPACT_ACTION_1) {
- case 1:
- compactActionSome = COMPACT_ACTION_FILE;
- break;
- case 2:
- compactActionSome = COMPACT_ACTION_ANON;
- break;
- case 3:
- compactActionSome = COMPACT_ACTION_FULL;
- break;
- default:
- compactActionSome = COMPACT_ACTION_FILE;
- break;
+ }
+
+ @VisibleForTesting
+ AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+ this(am);
+ mTestCallback = callback;
+ }
+
+ /**
+ * Reads phenotype config to determine whether app compaction is enabled or not and
+ * starts the background thread if necessary.
+ */
+ public void init() {
+ DeviceConfig.addOnPropertyChangedListener(DeviceConfig.ActivityManager.NAMESPACE,
+ ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
+ synchronized (mPhenotypeFlagLock) {
+ updateUseCompaction();
+ updateCompactionActions();
+ updateCompactionThrottles();
}
+ }
- switch(mConstants.COMPACT_ACTION_2) {
- case 1:
- compactActionFull = COMPACT_ACTION_FILE;
- break;
- case 2:
- compactActionFull = COMPACT_ACTION_ANON;
- break;
- case 3:
- compactActionFull = COMPACT_ACTION_FULL;
- break;
- default:
- compactActionFull = COMPACT_ACTION_FULL;
- break;
+ /**
+ * Returns whether compaction is enabled.
+ */
+ public boolean useCompaction() {
+ synchronized (mPhenotypeFlagLock) {
+ return mUseCompaction;
}
+ }
- throttleSomeSome = mConstants.COMPACT_THROTTLE_1;
- throttleSomeFull = mConstants.COMPACT_THROTTLE_2;
- throttleFullSome = mConstants.COMPACT_THROTTLE_3;
- throttleFullFull = mConstants.COMPACT_THROTTLE_4;
+ @GuardedBy("mAm")
+ void dump(PrintWriter pw) {
+ pw.println("AppCompactor settings");
+ synchronized (mPhenotypeFlagLock) {
+ pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
+ pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
+ pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull);
+ pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome);
+ pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
+ pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
+ pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
+ }
}
- // Must be called while holding AMS lock.
- final void compactAppSome(ProcessRecord app) {
+ @GuardedBy("mAm")
+ void compactAppSome(ProcessRecord app) {
app.reqCompactAction = COMPACT_PROCESS_SOME;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
@@ -128,8 +193,8 @@ public final class AppCompactor {
COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
}
- // Must be called while holding AMS lock.
- final void compactAppFull(ProcessRecord app) {
+ @GuardedBy("mAm")
+ void compactAppFull(ProcessRecord app) {
app.reqCompactAction = COMPACT_PROCESS_FULL;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
@@ -137,97 +202,205 @@ public final class AppCompactor {
COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
}
- final class MemCompactionHandler extends Handler {
- AppCompactor mAc;
- private MemCompactionHandler(AppCompactor ac) {
- super(ac.mCompactionThread.getLooper());
- mAc = ac;
+ /**
+ * Reads the flag value from DeviceConfig to determine whether app compaction
+ * should be enabled, and starts/stops the compaction thread as needed.
+ */
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUseCompaction() {
+ String useCompactionFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION);
+ mUseCompaction = TextUtils.isEmpty(useCompactionFlag)
+ ? DEFAULT_USE_COMPACTION : Boolean.parseBoolean(useCompactionFlag);
+ if (mUseCompaction && !mCompactionThread.isAlive()) {
+ mCompactionThread.start();
+ mCompactionHandler = new MemCompactionHandler();
+ }
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateCompactionActions() {
+ String compactAction1Flag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1);
+ String compactAction2Flag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2);
+
+ int compactAction1 = DEFAULT_COMPACT_ACTION_1;
+ try {
+ compactAction1 = TextUtils.isEmpty(compactAction1Flag)
+ ? DEFAULT_COMPACT_ACTION_1 : Integer.parseInt(compactAction1Flag);
+ } catch (NumberFormatException e) {
+ // Do nothing, leave default.
+ }
+
+ int compactAction2 = DEFAULT_COMPACT_ACTION_2;
+ try {
+ compactAction2 = TextUtils.isEmpty(compactAction2Flag)
+ ? DEFAULT_COMPACT_ACTION_2 : Integer.parseInt(compactAction2Flag);
+ } catch (NumberFormatException e) {
+ // Do nothing, leave default.
+ }
+
+ mCompactActionSome = compactActionIntToString(compactAction1);
+ mCompactActionFull = compactActionIntToString(compactAction2);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateCompactionThrottles() {
+ boolean useThrottleDefaults = false;
+ String throttleSomeSomeFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1);
+ String throttleSomeFullFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2);
+ String throttleFullSomeFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3);
+ String throttleFullFullFlag =
+ DeviceConfig.getProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4);
+
+ if (TextUtils.isEmpty(throttleSomeSomeFlag) || TextUtils.isEmpty(throttleSomeFullFlag)
+ || TextUtils.isEmpty(throttleFullSomeFlag)
+ || TextUtils.isEmpty(throttleFullFullFlag)) {
+ // Set defaults for all if any are not set.
+ useThrottleDefaults = true;
+ } else {
+ try {
+ mCompactThrottleSomeSome = Integer.parseInt(throttleSomeSomeFlag);
+ mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
+ mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
+ mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
+ } catch (NumberFormatException e) {
+ useThrottleDefaults = true;
+ }
+ }
+
+ if (useThrottleDefaults) {
+ mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1;
+ mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
+ mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
+ mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
+ }
+ }
+
+ @VisibleForTesting
+ static String compactActionIntToString(int action) {
+ switch(action) {
+ case COMPACT_ACTION_FILE_FLAG:
+ return COMPACT_ACTION_FILE;
+ case COMPACT_ACTION_ANON_FLAG:
+ return COMPACT_ACTION_ANON;
+ case COMPACT_ACTION_FULL_FLAG:
+ return COMPACT_ACTION_FULL;
+ default:
+ return COMPACT_ACTION_FILE;
+ }
+ }
+
+ private final class MemCompactionHandler extends Handler {
+ private MemCompactionHandler() {
+ super(mCompactionThread.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case COMPACT_PROCESS_MSG: {
- long start = SystemClock.uptimeMillis();
- ProcessRecord proc;
- int pid;
- String action;
- final String name;
- int pendingAction, lastCompactAction;
- long lastCompactTime;
- synchronized(mAc.mAm) {
- proc = mAc.mPendingCompactionProcesses.remove(0);
-
- // don't compact if the process has returned to perceptible
- if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
- return;
+ case COMPACT_PROCESS_MSG: {
+ long start = SystemClock.uptimeMillis();
+ ProcessRecord proc;
+ int pid;
+ String action;
+ final String name;
+ int pendingAction, lastCompactAction;
+ long lastCompactTime;
+ synchronized (mAm) {
+ proc = mPendingCompactionProcesses.remove(0);
+
+ // don't compact if the process has returned to perceptible
+ if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ return;
+ }
+
+ pid = proc.pid;
+ name = proc.processName;
+ pendingAction = proc.reqCompactAction;
+ lastCompactAction = proc.lastCompactAction;
+ lastCompactTime = proc.lastCompactTime;
}
- pid = proc.pid;
- name = proc.processName;
- pendingAction = proc.reqCompactAction;
- lastCompactAction = proc.lastCompactAction;
- lastCompactTime = proc.lastCompactTime;
- }
- if (pid == 0) {
- // not a real process, either one being launched or one being killed
- return;
- }
-
- // basic throttling
- // use the ActivityManagerConstants knobs to determine whether current/prevous
- // compaction combo should be throtted or not
- if (pendingAction == COMPACT_PROCESS_SOME) {
- if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleSomeSome)) ||
- (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleSomeFull))) {
+ if (pid == 0) {
+ // not a real process, either one being launched or one being killed
return;
}
- } else {
- if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < throttleFullSome)) ||
- (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < throttleFullFull))) {
- return;
+
+ // basic throttling
+ // use the Phenotype flag knobs to determine whether current/prevous
+ // compaction combo should be throtted or not
+
+ // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
+ // should very seldom change, and taking the risk of using the wrong action is
+ // preferable to taking the lock for every single compaction action.
+ if (pendingAction == COMPACT_PROCESS_SOME) {
+ if ((lastCompactAction == COMPACT_PROCESS_SOME
+ && (start - lastCompactTime < mCompactThrottleSomeSome))
+ || (lastCompactAction == COMPACT_PROCESS_FULL
+ && (start - lastCompactTime
+ < mCompactThrottleSomeFull))) {
+ return;
+ }
+ } else {
+ if ((lastCompactAction == COMPACT_PROCESS_SOME
+ && (start - lastCompactTime < mCompactThrottleFullSome))
+ || (lastCompactAction == COMPACT_PROCESS_FULL
+ && (start - lastCompactTime
+ < mCompactThrottleFullFull))) {
+ return;
+ }
}
- }
- try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " +
- ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") +
- ": " + name);
- long[] rssBefore = Process.getRss(pid);
- FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
if (pendingAction == COMPACT_PROCESS_SOME) {
- action = compactActionSome;
+ action = mCompactActionSome;
} else {
- action = compactActionFull;
+ action = mCompactActionFull;
}
- fos.write(action.getBytes());
- fos.close();
- long[] rssAfter = Process.getRss(pid);
- long end = SystemClock.uptimeMillis();
- long time = end - start;
- EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
- rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
- rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
- lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
- StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
- rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
- rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
- lastCompactAction, lastCompactTime, msg.arg1,
- ActivityManager.processStateAmToProto(msg.arg2));
- synchronized(mAc.mAm) {
- proc.lastCompactTime = end;
- proc.lastCompactAction = pendingAction;
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
+ + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
+ + ": " + name);
+ long[] rssBefore = Process.getRss(pid);
+ FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+ fos.write(action.getBytes());
+ fos.close();
+ long[] rssAfter = Process.getRss(pid);
+ long end = SystemClock.uptimeMillis();
+ long time = end - start;
+ EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
+ rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+ rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+ lastCompactAction, lastCompactTime, msg.arg1, msg.arg2);
+ StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
+ rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+ rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
+ lastCompactAction, lastCompactTime, msg.arg1,
+ ActivityManager.processStateAmToProto(msg.arg2));
+ synchronized (mAm) {
+ proc.lastCompactTime = end;
+ proc.lastCompactAction = pendingAction;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ } catch (Exception e) {
+ // nothing to do, presumably the process died
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } catch (Exception e) {
- // nothing to do, presumably the process died
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
- }
}
}
-
-
}
diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java
index aabb5877764e..dc9a4bf1ad94 100644
--- a/services/core/java/com/android/server/am/BaseErrorDialog.java
+++ b/services/core/java/com/android/server/am/BaseErrorDialog.java
@@ -16,8 +16,6 @@
package com.android.server.am;
-import com.android.internal.R;
-
import android.app.AlertDialog;
import android.content.Context;
import android.os.Handler;
@@ -26,6 +24,8 @@ import android.view.KeyEvent;
import android.view.WindowManager;
import android.widget.Button;
+import com.android.internal.R;
+
public class BaseErrorDialog extends AlertDialog {
private static final int ENABLE_BUTTONS = 0;
private static final int DISABLE_BUTTONS = 1;
@@ -36,7 +36,7 @@ public class BaseErrorDialog extends AlertDialog {
super(context, com.android.internal.R.style.Theme_DeviceDefault_Dialog_AppError);
context.assertRuntimeOverlayThemable();
- getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
WindowManager.LayoutParams attrs = getWindow().getAttributes();
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a376e7a15410..08900328a200 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -19,6 +19,7 @@ package com.android.server.am;
import android.app.ActivityManager;
import android.app.job.JobProtoEnums;
import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -46,6 +47,7 @@ import android.os.connectivity.WifiBatteryStats;
import android.os.health.HealthStatsParceler;
import android.os.health.HealthStatsWriter;
import android.os.health.UidHealthStats;
+import android.provider.Settings;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.SignalStrength;
@@ -1651,4 +1653,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return new HealthStatsParceler(uidWriter);
}
+ /**
+ * Delay for sending ACTION_CHARGING after device is plugged in.
+ *
+ * @hide
+ */
+ public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null);
+ final long ident = Binder.clearCallingIdentity();
+
+ try {
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ return Settings.Global.putLong(contentResolver,
+ Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+ delayMillis);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 353749f211c1..cdf6e0ede865 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -776,6 +776,7 @@ public final class BroadcastQueue {
final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index d3953b58296c..3d69aa8088f5 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -61,6 +61,8 @@ final class CoreSettingsObserver extends ContentObserver {
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class);
sGlobalSettingToTypeMap.put(
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
+ sGlobalSettingToTypeMap.put(
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index be910d46a8ad..1e03f6c3ba5f 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -134,10 +134,11 @@ public final class OomAdjuster {
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
- // mConstants can be null under test, which causes AppCompactor to crash
- if (mConstants != null) {
- mAppCompact = new AppCompactor(mService);
- }
+ mAppCompact = new AppCompactor(mService);
+ }
+
+ void initSettings() {
+ mAppCompact.init();
}
/**
@@ -1679,7 +1680,7 @@ public final class OomAdjuster {
if (app.curAdj != app.setAdj) {
// don't compact during bootup
- if (mConstants.USE_COMPACTION && mService.mBooted) {
+ if (mAppCompact.useCompaction() && mService.mBooted) {
// Perform a minor compaction when a perceptible app becomes the prev/home app
// Perform a major compaction when any app enters cached
// reminder: here, setAdj is previous state, curAdj is upcoming state
@@ -2104,4 +2105,8 @@ public final class OomAdjuster {
+ " mNewNumServiceProcs=" + mNewNumServiceProcs);
}
+ @GuardedBy("mService")
+ void dumpAppCompactorSettings(PrintWriter pw) {
+ mAppCompact.dump(pw);
+ }
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 55cca950a213..0b27a8ad77dc 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -379,6 +379,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
if (userId == UserHandle.USER_CURRENT) {
userId = controller.mUserController.getCurrentOrTargetUserId();
}
+ // temporarily allow receivers and services to open activities from background if the
+ // PendingIntent.send() caller was foreground at the time of sendInner() call
+ final boolean allowTrampoline = uid != callingUid
+ && controller.mAtmInternal.isUidForeground(callingUid);
switch (key.type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
@@ -419,7 +423,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
uid, finalIntent, resolvedType, finishedReceiver, code, null, null,
requiredPermission, options, (finishedReceiver != null),
false, userId,
- mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken));
+ mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken)
+ || allowTrampoline);
if (sent == ActivityManager.BROADCAST_SUCCESS) {
sendFinish = false;
}
@@ -433,7 +438,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,
key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,
key.packageName, userId,
- mAllowBgActivityStartsForServiceSender.contains(whitelistToken));
+ mAllowBgActivityStartsForServiceSender.contains(whitelistToken)
+ || allowTrampoline);
} catch (RuntimeException e) {
Slog.w(TAG, "Unable to send startService intent", e);
} catch (TransactionTooLargeException e) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c69803965ac9..003ddd13f152 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1502,7 +1502,7 @@ public final class ProcessList {
mService.mNativeDebuggingApp = null;
}
- if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0
+ if (app.info.isCodeIntegrityPreferred()
|| (app.info.isPrivilegedApp()
&& DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
@@ -2384,14 +2384,14 @@ public final class ProcessList {
}
@GuardedBy("mService")
- void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) {
+ void setAllHttpProxyLocked() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
// Don't dispatch to isolated processes as they can't access
// ConnectivityManager and don't have network privileges anyway.
if (r.thread != null && !r.isolated) {
try {
- r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
+ r.thread.updateHttpProxy();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update http proxy for: " +
r.info.processName);
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 27edbbf4f2d5..b71a7517ab12 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -174,10 +174,11 @@ public class AttentionManagerService extends SystemService {
@Override
public void onSuccess(int requestCode, int result, long timestamp) {
callback.onSuccess(requestCode, result, timestamp);
- userState.mAttentionCheckCache = new AttentionCheckCache(
- SystemClock.uptimeMillis(), result,
- timestamp);
-
+ synchronized (mLock) {
+ userState.mAttentionCheckCache = new AttentionCheckCache(
+ SystemClock.uptimeMillis(), result,
+ timestamp);
+ }
StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
result);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
new file mode 100644
index 000000000000..d652f93ccf04
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -0,0 +1,807 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/** @hide */
+/*package*/ final class AudioDeviceBroker {
+
+ private static final String TAG = "AudioDeviceBroker";
+
+ private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s
+
+ /*package*/ static final int BTA2DP_DOCK_TIMEOUT_MS = 8000;
+ // Timeout for connection to bluetooth headset service
+ /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
+ private final @NonNull AudioService mAudioService;
+ private final @NonNull Context mContext;
+
+ /** Forced device usage for communications sent to AudioSystem */
+ private int mForcedUseForComm;
+ /**
+ * Externally reported force device usage state returned by getters: always consistent
+ * with requests by setters */
+ private int mForcedUseForCommExt;
+
+ // Manages all connected devices, only ever accessed on the message loop
+ //### or make it synchronized
+ private final AudioDeviceInventory mDeviceInventory;
+ // Manages notifications to BT service
+ private final BtHelper mBtHelper;
+
+
+ //-------------------------------------------------------------------
+ private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+ private static long sLastDeviceConnectMsgTime = 0;
+
+ private final Object mBluetoothA2dpEnabledLock = new Object();
+ // Request to override default use of A2DP for media.
+ @GuardedBy("mBluetoothA2dpEnabledLock")
+ private boolean mBluetoothA2dpEnabled;
+
+ // lock always taken synchronized on mConnectedDevices
+ /*package*/ final Object mA2dpAvrcpLock = new Object();
+ // lock always taken synchronized on mConnectedDevices
+ /*package*/ final Object mHearingAidLock = new Object();
+
+ // lock always taken when accessing AudioService.mSetModeDeathHandlers
+ /*package*/ final Object mSetModeLock = new Object();
+
+ //-------------------------------------------------------------------
+ /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+ mContext = context;
+ mAudioService = service;
+ setupMessaging(context);
+ mBtHelper = new BtHelper(this);
+ mDeviceInventory = new AudioDeviceInventory(this);
+
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ mForcedUseForCommExt = mForcedUseForComm;
+
+ }
+
+ /*package*/ Context getContext() {
+ return mContext;
+ }
+
+ //---------------------------------------------------------------------
+ // Communication from AudioService
+ // All methods are asynchronous and never block
+ // All permission checks are done in AudioService, all incoming calls are considered "safe"
+ // All post* methods are asynchronous
+
+ /*package*/ void onSystemReady() {
+ mBtHelper.onSystemReady();
+ }
+
+ /*package*/ void onAudioServerDied() {
+ // Restore forced usage for communications and record
+ onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
+ onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+ // restore devices
+ sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
+ }
+
+ /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+ useCase, config, eventSource);
+ }
+
+ /*package*/ void toggleHdmiIfConnected_Async() {
+ sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
+ }
+
+ /*package*/ void disconnectAllBluetoothProfiles() {
+ mBtHelper.disconnectAllBluetoothProfiles();
+ }
+
+ /**
+ * Handle BluetoothHeadset intents where the action is one of
+ * {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
+ * {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
+ * @param intent
+ */
+ /*package*/ void receiveBtEvent(@NonNull Intent intent) {
+ mBtHelper.receiveBtEvent(intent);
+ }
+
+ /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ if (mBluetoothA2dpEnabled == on) {
+ return;
+ }
+ mBluetoothA2dpEnabled = on;
+ mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ source);
+ }
+ }
+
+ /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) {
+ if (on) {
+ if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+ }
+ mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+
+ mForcedUseForCommExt = mForcedUseForComm;
+ setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+ }
+
+ /*package*/ boolean isSpeakerphoneOn() {
+ return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+ }
+
+ /*package*/ void setWiredDeviceConnectionState(int type,
+ @AudioService.ConnectionState int state, String address, String name,
+ String caller) {
+ //TODO move logging here just like in setBluetooth* methods
+ mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+ }
+
+ /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
+ // only querying address as this is the only readily available field
+ // on the device
+ + " addr=" + device.getAddress()
+ + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
+ + " vol=" + a2dpVolume)).printLog(TAG));
+ if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+ new BtHelper.BluetoothA2dpDeviceInfo(device))) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP connection state ignored"));
+ return 0;
+ }
+ return mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+ device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+ }
+
+ /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+ @NonNull BluetoothDevice device,
+ @AudioService.BtProfileConnectionState int state, int profile,
+ boolean suppressNoisyIntent, int a2dpVolume) {
+ return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+ suppressNoisyIntent, a2dpVolume);
+ }
+
+ /*package*/ int setBluetoothHearingAidDeviceConnectionState(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "setHearingAidDeviceConnectionState state=" + state
+ + " addr=" + device.getAddress()
+ + " supprNoisy=" + suppressNoisyIntent
+ + " src=" + eventSource)).printLog(TAG));
+ return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+ device, state, suppressNoisyIntent, musicDevice);
+ }
+
+ // never called by system components
+ /*package*/ void setBluetoothScoOnByApp(boolean on) {
+ mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+ }
+
+ /*package*/ boolean isBluetoothScoOnForApp() {
+ return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
+ }
+
+ /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
+ //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+ if (on) {
+ // do not accept SCO ON if SCO audio is not connected
+ if (!mBtHelper.isBluetoothScoOn()) {
+ mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+ return;
+ }
+ mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+ mForcedUseForCommExt = mForcedUseForComm;
+ AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+ sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
+ // Un-mute ringtone stream volume
+ mAudioService.setUpdateRingerModeServiceInt();
+ }
+
+ /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+ return mDeviceInventory.startWatchingRoutes(observer);
+ }
+
+ /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+ return mDeviceInventory.getCurAudioRoutes();
+ }
+
+ /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+ synchronized (mA2dpAvrcpLock) {
+ return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+ }
+ }
+
+ /*package*/ boolean isBluetoothA2dpOn() {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ return mBluetoothA2dpEnabled;
+ }
+ }
+
+ /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
+ sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
+ }
+
+ /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
+ sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
+ }
+
+ /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
+ sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+ }
+
+ /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
+ sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+ }
+
+ /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+ @NonNull String eventSource) {
+ mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
+ }
+
+ /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
+ mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+ }
+
+ //---------------------------------------------------------------------
+ // Communication with (to) AudioService
+ //TODO check whether the AudioService methods are candidates to move here
+ /*package*/ void postAccessoryPlugMediaUnmute(int device) {
+ mAudioService.postAccessoryPlugMediaUnmute(device);
+ }
+
+ /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) {
+ return mAudioService.getStreamState(streamType);
+ }
+
+ /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() {
+ return mAudioService.mSetModeDeathHandlers;
+ }
+
+ /*package*/ int getDeviceForStream(int streamType) {
+ return mAudioService.getDeviceForStream(streamType);
+ }
+
+ /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) {
+ mAudioService.setDeviceVolume(streamState, device);
+ }
+
+ /*packages*/ void observeDevicesForAllStreams() {
+ mAudioService.observeDevicesForAllStreams();
+ }
+
+ /*package*/ boolean isInCommunication() {
+ return mAudioService.isInCommunication();
+ }
+
+ /*package*/ boolean hasMediaDynamicPolicy() {
+ return mAudioService.hasMediaDynamicPolicy();
+ }
+
+ /*package*/ ContentResolver getContentResolver() {
+ return mAudioService.getContentResolver();
+ }
+
+ /*package*/ void checkMusicActive(int deviceType, String caller) {
+ mAudioService.checkMusicActive(deviceType, caller);
+ }
+
+ /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+ mAudioService.checkVolumeCecOnHdmiConnection(state, caller);
+ }
+
+ //---------------------------------------------------------------------
+ // Message handling on behalf of helper classes
+ /*package*/ void broadcastScoConnectionState(int state) {
+ sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
+ }
+
+ /*package*/ void broadcastBecomingNoisy() {
+ sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
+ }
+
+ //###TODO unify with handleSetA2dpSinkConnectionState
+ /*package*/ void postA2dpSinkConnection(int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+ sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+ state, btDeviceInfo, delay);
+ }
+
+ //###TODO unify with handleSetA2dpSourceConnectionState
+ /*package*/ void postA2dpSourceConnection(int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+ sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+ state, btDeviceInfo, delay);
+ }
+
+ /*package*/ void postSetWiredDeviceConnectionState(
+ AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
+ sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
+ }
+
+ /*package*/ void postSetHearingAidConnectionState(
+ @AudioService.BtProfileConnectionState int state,
+ @NonNull BluetoothDevice device, int delay) {
+ sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+ state,
+ device,
+ delay);
+ }
+
+ //---------------------------------------------------------------------
+ // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
+ // only call from a "handle"* method or "on"* method
+
+ // Handles request to override default use of A2DP for media.
+ //@GuardedBy("mConnectedDevices")
+ /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) {
+ // for logging only
+ final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).append(" src:").append(source).toString();
+
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBluetoothA2dpEnabled = on;
+ mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+ onSetForceUse(
+ AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ eventSource);
+ }
+ }
+
+ /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+ String deviceName) {
+ return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+ }
+
+ /*package*/ void handleDisconnectA2dp() {
+ mDeviceInventory.disconnectA2dp();
+ }
+ /*package*/ void handleDisconnectA2dpSink() {
+ mDeviceInventory.disconnectA2dpSink();
+ }
+
+ /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+ final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version?
+ // might be ok here because called on BT thread? + sync happening in
+ // checkSendBecomingNoisyIntent
+ final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
+ AudioSystem.DEVICE_NONE);
+ final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
+ + " state= " + state
+ + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
+ }
+ sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+ state, btDeviceInfo, delay);
+ }
+
+ /*package*/ void handleDisconnectHearingAid() {
+ mDeviceInventory.disconnectHearingAid();
+ }
+
+ /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+ final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+ btDeviceInfo);
+ }
+
+ /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
+ sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
+ }
+
+ /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
+ mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+ }
+
+ /*package*/ void postReportNewRoutes() {
+ sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
+ }
+
+ /*package*/ void cancelA2dpDockTimeout() {
+ mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+ }
+
+ /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+ sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
+ }
+
+ //###
+ // must be called synchronized on mConnectedDevices
+ /*package*/ boolean hasScheduledA2dpDockTimeout() {
+ return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+ }
+
+ //###
+ // must be called synchronized on mConnectedDevices
+ /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
+ return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+ new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
+ }
+
+ /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
+ sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+ }
+
+ /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+ synchronized (mA2dpAvrcpLock) {
+ mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+ }
+ }
+
+ /*package*/ boolean getBluetoothA2dpEnabled() {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ return mBluetoothA2dpEnabled;
+ }
+ }
+
+ /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+ synchronized (mA2dpAvrcpLock) {
+ return mBtHelper.getA2dpCodec(device);
+ }
+ }
+
+ //---------------------------------------------------------------------
+ // Internal handling of messages
+ // These methods are ALL synchronous, in response to message handling in BrokerHandler
+ // Blocking in any of those will block the message queue
+
+ private void onSetForceUse(int useCase, int config, String eventSource) {
+ if (useCase == AudioSystem.FOR_MEDIA) {
+ postReportNewRoutes();
+ }
+ AudioService.sForceUseLogger.log(
+ new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+ AudioSystem.setForceUse(useCase, config);
+ }
+
+ private void onSendBecomingNoisyIntent() {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
+ sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+ }
+
+ //---------------------------------------------------------------------
+ // Message handling
+ private BrokerHandler mBrokerHandler;
+ private BrokerThread mBrokerThread;
+ private PowerManager.WakeLock mBrokerEventWakeLock;
+
+ private void setupMessaging(Context ctxt) {
+ final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE);
+ mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "handleAudioDeviceEvent");
+ mBrokerThread = new BrokerThread();
+ mBrokerThread.start();
+ waitForBrokerHandlerCreation();
+ }
+
+ private void waitForBrokerHandlerCreation() {
+ synchronized (this) {
+ while (mBrokerHandler == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interruption while waiting on BrokerHandler");
+ }
+ }
+ }
+ }
+
+ /** Class that handles the device broker's message queue */
+ private class BrokerThread extends Thread {
+ BrokerThread() {
+ super("AudioDeviceBroker");
+ }
+
+ @Override
+ public void run() {
+ // Set this thread up so the handler will work on it
+ Looper.prepare();
+
+ synchronized (AudioDeviceBroker.this) {
+ mBrokerHandler = new BrokerHandler();
+
+ // Notify that the handler has been created
+ AudioDeviceBroker.this.notify();
+ }
+
+ Looper.loop();
+ }
+ }
+
+ /** Class that handles the message queue */
+ private class BrokerHandler extends Handler {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RESTORE_DEVICES:
+ mDeviceInventory.onRestoreDevices();
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBtHelper.onAudioServerDiedRestoreA2dp();
+ }
+ break;
+ case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+ mDeviceInventory.onSetWiredDeviceConnectionState(
+ (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+ break;
+ case MSG_I_BROADCAST_BT_CONNECTION_STATE:
+ mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+ break;
+ case MSG_IIL_SET_FORCE_USE: // intented fall-through
+ case MSG_IIL_SET_FORCE_BT_A2DP_USE:
+ onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
+ break;
+ case MSG_REPORT_NEW_ROUTES:
+ mDeviceInventory.onReportNewRoutes();
+ break;
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ mDeviceInventory.onSetA2dpSinkConnectionState(
+ (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2);
+ break;
+ case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+ mDeviceInventory.onSetA2dpSourceConnectionState(
+ (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+ break;
+ case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+ mDeviceInventory.onSetHearingAidConnectionState(
+ (BluetoothDevice) msg.obj, msg.arg1);
+ break;
+ case MSG_BT_HEADSET_CNCT_FAILED:
+ mBtHelper.resetBluetoothSco();
+ break;
+ case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ // msg.obj == address of BTA2DP device
+ mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+ break;
+ case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ final int a2dpCodec;
+ final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+ synchronized (mA2dpAvrcpLock) {
+ a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+ }
+ mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
+ new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec));
+ break;
+ case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
+ onSendBecomingNoisyIntent();
+ break;
+ case MSG_II_SET_HEARING_AID_VOLUME:
+ mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+ break;
+ case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
+ mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+ break;
+ case MSG_I_DISCONNECT_BT_SCO:
+ mBtHelper.disconnectBluetoothSco(msg.arg1);
+ break;
+ case MSG_TOGGLE_HDMI:
+ mDeviceInventory.onToggleHdmi();
+ break;
+ case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+ mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+ (BtHelper.BluetoothA2dpDeviceInfo) msg.obj);
+ break;
+ default:
+ Log.wtf(TAG, "Invalid message " + msg.what);
+ }
+ if (isMessageHandledUnderWakelock(msg.what)) {
+ try {
+ mBrokerEventWakeLock.release();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception releasing wakelock", e);
+ }
+ }
+ }
+ }
+
+ // List of all messages. If a message has be handled under wakelock, add it to
+ // the isMessageHandledUnderWakelock(int) method
+ // Naming of msg indicates arguments, using JNI argument grammar
+ // (e.g. II indicates two int args, IL indicates int and Obj arg)
+ private static final int MSG_RESTORE_DEVICES = 1;
+ private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
+ private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
+ private static final int MSG_IIL_SET_FORCE_USE = 4;
+ private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
+ private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
+ private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
+ private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
+ private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
+ private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+ private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+ private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
+ private static final int MSG_REPORT_NEW_ROUTES = 13;
+ private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
+ private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
+ private static final int MSG_I_DISCONNECT_BT_SCO = 16;
+ private static final int MSG_TOGGLE_HDMI = 17;
+ private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
+
+
+ private static boolean isMessageHandledUnderWakelock(int msgId) {
+ switch(msgId) {
+ case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+ case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+ case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ case MSG_TOGGLE_HDMI:
+ case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // Message helper methods
+
+ // sendMsg() flags
+ /** If the msg is already queued, replace it with this one. */
+ private static final int SENDMSG_REPLACE = 0;
+ /** If the msg is already queued, ignore this one and leave the old. */
+ private static final int SENDMSG_NOOP = 1;
+ /** If the msg is already queued, queue this one and leave the old. */
+ private static final int SENDMSG_QUEUE = 2;
+
+ private void sendMsg(int msg, int existingMsgPolicy, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
+ }
+
+ private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
+ }
+
+ private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
+ }
+
+ private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
+ }
+
+ private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
+ }
+
+ private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
+ }
+
+ private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
+ sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
+ }
+
+ private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
+ sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
+ }
+
+ private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
+ sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
+ }
+
+ private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
+ sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
+ }
+
+ private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
+ int delay) {
+ if (existingMsgPolicy == SENDMSG_REPLACE) {
+ mBrokerHandler.removeMessages(msg);
+ } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) {
+ return;
+ }
+
+ if (isMessageHandledUnderWakelock(msg)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception acquiring wakelock", e);
+ }
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ synchronized (sLastDeviceConnectionMsgTimeLock) {
+ long time = SystemClock.uptimeMillis() + delay;
+
+ switch (msg) {
+ case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+ case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+ case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+ case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+ case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+ if (sLastDeviceConnectMsgTime >= time) {
+ // add a little delay to make sure messages are ordered as expected
+ time = sLastDeviceConnectMsgTime + 30;
+ }
+ sLastDeviceConnectMsgTime = time;
+ break;
+ default:
+ break;
+ }
+
+ mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+ time);
+ }
+ }
+
+ //-------------------------------------------------------------
+ // internal utilities
+ private void sendBroadcastToAll(Intent intent) {
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
new file mode 100644
index 000000000000..eb76e6e02edc
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -0,0 +1,1024 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioDevicePort;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPort;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/**
+ * Class to manage the inventory of all connected devices.
+ * This class is thread-safe.
+ */
+public final class AudioDeviceInventory {
+
+ private static final String TAG = "AS.AudioDeviceInventory";
+
+ // Actual list of connected devices
+ // Key for map created from DeviceInfo.makeDeviceListKey()
+ private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
+
+ private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
+ mDeviceBroker = broker;
+ }
+
+ // cache of the address of the last dock the device was connected to
+ private String mDockAddress;
+
+ // Monitoring of audio routes. Protected by mAudioRoutes.
+ final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
+ final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
+ new RemoteCallbackList<IAudioRoutesObserver>();
+
+ //------------------------------------------------------------
+ /**
+ * Class to store info about connected devices.
+ * Use makeDeviceListKey() to make a unique key for this list.
+ */
+ private static class DeviceInfo {
+ final int mDeviceType;
+ final String mDeviceName;
+ final String mDeviceAddress;
+ int mDeviceCodecFormat;
+
+ DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+ mDeviceType = deviceType;
+ mDeviceName = deviceName;
+ mDeviceAddress = deviceAddress;
+ mDeviceCodecFormat = deviceCodecFormat;
+ }
+
+ @Override
+ public String toString() {
+ return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+ + " name:" + mDeviceName
+ + " addr:" + mDeviceAddress
+ + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+ }
+
+ /**
+ * 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) {
+ return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
+ }
+ }
+
+ /**
+ * A class just for packaging up a set of connection parameters.
+ */
+ /*package*/ class WiredDeviceConnectionState {
+ public final int mType;
+ public final @AudioService.ConnectionState int mState;
+ public final String mAddress;
+ public final String mName;
+ public final String mCaller;
+
+ /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+ String address, String name, String caller) {
+ mType = type;
+ mState = state;
+ mAddress = address;
+ mName = name;
+ mCaller = caller;
+ }
+ }
+
+ //------------------------------------------------------------
+ // Message handling from AudioDeviceBroker
+
+ /**
+ * Restore previously connected devices. Use in case of audio server crash
+ * (see AudioService.onAudioServerDied() method)
+ */
+ /*package*/ void onRestoreDevices() {
+ synchronized (mConnectedDevices) {
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceInfo di = mConnectedDevices.valueAt(i);
+ AudioSystem.setDeviceConnectionState(
+ di.mDeviceType,
+ AudioSystem.DEVICE_STATE_AVAILABLE,
+ di.mDeviceAddress,
+ di.mDeviceName,
+ di.mDeviceCodecFormat);
+ }
+ }
+ }
+
+ /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
+ @AudioService.BtProfileConnectionState int state, int a2dpVolume) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
+ + state + " is dock=" + btDevice.isBluetoothDock());
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP sink connected: device addr=" + address + " state=" + state));
+
+ final int a2dpCodec;
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ a2dpCodec = btInfo.getCodec();
+ }
+
+ synchronized (mConnectedDevices) {
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ btDevice.getAddress());
+ final DeviceInfo di = mConnectedDevices.get(key);
+ boolean isConnected = di != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ if (btDevice.isBluetoothDock()) {
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ // introduction of a delay for transient disconnections of docks when
+ // power is rapidly turned off/on, this message will be canceled if
+ // we reconnect the dock under a preset delay
+ makeA2dpDeviceUnavailableLater(address,
+ AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
+ // the next time isConnected is evaluated, it will be false for the dock
+ }
+ } else {
+ makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+ }
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ if (btDevice.isBluetoothDock()) {
+ // this could be a reconnection after a transient disconnection
+ mDeviceBroker.cancelA2dpDockTimeout();
+ mDockAddress = address;
+ } else {
+ // this could be a connection of another A2DP device before the timeout of
+ // a dock: cancel the dock timeout, and make the dock unavailable now
+ if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
+ mDeviceBroker.cancelA2dpDockTimeout();
+ makeA2dpDeviceUnavailableNow(mDockAddress,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+ }
+ if (a2dpVolume != -1) {
+ AudioService.VolumeStreamState streamState =
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+ // Convert index to internal representation in VolumeStreamState
+ a2dpVolume = a2dpVolume * 10;
+ streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ "onSetA2dpSinkConnectionState");
+ mDeviceBroker.setDeviceVolume(
+ streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ }
+ makeA2dpDeviceAvailable(address, btDevice.getName(),
+ "onSetA2dpSinkConnectionState", a2dpCodec);
+ }
+ }
+ }
+
+ /*package*/ void onSetA2dpSourceConnectionState(
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
+ + state);
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+
+ synchronized (mConnectedDevices) {
+ final String key = DeviceInfo.makeDeviceListKey(
+ AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+ final DeviceInfo di = mConnectedDevices.get(key);
+ boolean isConnected = di != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ makeA2dpSrcUnavailable(address);
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ makeA2dpSrcAvailable(address);
+ }
+ }
+ }
+
+ /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
+ @AudioService.BtProfileConnectionState int state) {
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "onSetHearingAidConnectionState addr=" + address));
+
+ synchronized (mConnectedDevices) {
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
+ btDevice.getAddress());
+ final DeviceInfo di = mConnectedDevices.get(key);
+ boolean isConnected = di != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ makeHearingAidDeviceUnavailable(address);
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ makeHearingAidDeviceAvailable(address, btDevice.getName(),
+ "onSetHearingAidConnectionState");
+ }
+ }
+ }
+
+ /*package*/ void onBluetoothA2dpDeviceConfigChange(
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+ }
+ if (btDevice == null) {
+ return;
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "onBluetoothA2dpDeviceConfigChange addr=" + address));
+
+ final int a2dpCodec = btInfo.getCodec();
+
+ synchronized (mConnectedDevices) {
+ if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2dp config change ignored"));
+ return;
+ }
+ final String key =
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+ return;
+ }
+ // Device is connected
+ if (di.mDeviceCodecFormat != a2dpCodec) {
+ di.mDeviceCodecFormat = a2dpCodec;
+ mConnectedDevices.replace(key, di);
+ }
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
+ + di.mDeviceCodecFormat);
+ }
+ if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+ btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) {
+ // force A2DP device disconnection in case of error so that AudioService state
+ // is consistent with audio policy manager state
+ final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ setBluetoothA2dpDeviceConnectionState(
+ btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+ false /* suppressNoisyIntent */, musicDevice,
+ -1 /* a2dpVolume */);
+ }
+ }
+ }
+
+ /*package*/ void onBluetoothA2dpActiveDeviceChange(
+ @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+ final BluetoothDevice btDevice = btInfo.getBtDevice();
+ int a2dpVolume = btInfo.getVolume();
+ final int a2dpCodec = btInfo.getCodec();
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "onBluetoothA2dpActiveDeviceChange addr=" + address));
+
+ synchronized (mConnectedDevices) {
+ //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
+ // for this type of message
+ if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2dp config change ignored"));
+ return;
+ }
+ final String key = DeviceInfo.makeDeviceListKey(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ return;
+ }
+
+ // Device is connected
+ if (a2dpVolume != -1) {
+ final AudioService.VolumeStreamState streamState =
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+ // Convert index to internal representation in VolumeStreamState
+ a2dpVolume = a2dpVolume * 10;
+ streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ "onBluetoothA2dpActiveDeviceChange");
+ mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ }
+
+ if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+ btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+ int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ // force A2DP device disconnection in case of error so that AudioService state is
+ // consistent with audio policy manager state
+ setBluetoothA2dpDeviceConnectionState(
+ btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+ false /* suppressNoisyIntent */, musicDevice,
+ -1 /* a2dpVolume */);
+ }
+ }
+ }
+
+ /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+ synchronized (mConnectedDevices) {
+ makeA2dpDeviceUnavailableNow(address, a2dpCodec);
+ }
+ }
+
+ /*package*/ void onReportNewRoutes() {
+ int n = mRoutesObservers.beginBroadcast();
+ if (n > 0) {
+ AudioRoutesInfo routes;
+ synchronized (mCurAudioRoutes) {
+ routes = new AudioRoutesInfo(mCurAudioRoutes);
+ }
+ while (n > 0) {
+ n--;
+ IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
+ try {
+ obs.dispatchAudioRoutesChanged(routes);
+ } catch (RemoteException e) { }
+ }
+ }
+ mRoutesObservers.finishBroadcast();
+ mDeviceBroker.observeDevicesForAllStreams();
+ }
+
+ private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
+
+ /*package*/ void onSetWiredDeviceConnectionState(
+ AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
+ AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+
+ synchronized (mConnectedDevices) {
+ if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
+ && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
+ mDeviceBroker.setBluetoothA2dpOnInt(true,
+ "onSetWiredDeviceConnectionState state DISCONNECTED");
+ }
+
+ if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress,
+ wdcs.mName)) {
+ // change of connection state failed, bailout
+ return;
+ }
+ if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
+ if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
+ mDeviceBroker.setBluetoothA2dpOnInt(false,
+ "onSetWiredDeviceConnectionState state not DISCONNECTED");
+ }
+ mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
+ }
+ mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
+ sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
+ updateAudioRoutes(wdcs.mType, wdcs.mState);
+ }
+ }
+
+ /*package*/ void onToggleHdmi() {
+ synchronized (mConnectedDevices) {
+ // Is HDMI connected?
+ final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
+ final DeviceInfo di = mConnectedDevices.get(key);
+ if (di == null) {
+ Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
+ return;
+ }
+ // Toggle HDMI to retrigger broadcast with proper formats.
+ setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
+ "android"); // disconnect
+ setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+ AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
+ "android"); // reconnect
+ }
+ }
+ //------------------------------------------------------------
+ //
+
+ /**
+ * Implements the communication with AudioSystem to (dis)connect a device in the native layers
+ * @param connect true if connection
+ * @param device the device type
+ * @param address the address of the device
+ * @param deviceName human-readable name of device
+ * @return false if an error was reported by AudioSystem
+ */
+ /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+ String deviceName) {
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ + Integer.toHexString(device) + " address:" + address
+ + " name:" + deviceName + ")");
+ }
+ synchronized (mConnectedDevices) {
+ final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "deviceKey:" + deviceKey);
+ }
+ DeviceInfo di = mConnectedDevices.get(deviceKey);
+ boolean isConnected = di != null;
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
+ }
+ if (connect && !isConnected) {
+ final int res = AudioSystem.setDeviceConnectionState(device,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
+ + " due to command error " + res);
+ return false;
+ }
+ mConnectedDevices.put(deviceKey, new DeviceInfo(
+ device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(device);
+ return true;
+ } else if (!connect && isConnected) {
+ AudioSystem.setDeviceConnectionState(device,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ // always remove even if disconnection failed
+ mConnectedDevices.remove(deviceKey);
+ return true;
+ }
+ Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+ + ", deviceSpec=" + di + ", connect=" + connect);
+ }
+ return false;
+ }
+
+
+ /*package*/ void disconnectA2dp() {
+ synchronized (mConnectedDevices) {
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ if (toRemove.size() > 0) {
+ final int delay = checkSendBecomingNoisyIntentInt(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ 0, AudioSystem.DEVICE_NONE);
+ toRemove.stream().forEach(deviceAddress ->
+ makeA2dpDeviceUnavailableLater(deviceAddress, delay)
+ );
+ }
+ }
+ }
+ }
+
+ /*package*/ void disconnectA2dpSink() {
+ synchronized (mConnectedDevices) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
+ }
+ }
+
+ /*package*/ void disconnectHearingAid() {
+ synchronized (mConnectedDevices) {
+ synchronized (mDeviceBroker.mHearingAidLock) {
+ final ArraySet<String> toRemove = new ArraySet<>();
+ // Disconnect ALL DEVICE_OUT_HEARING_AID devices
+ mConnectedDevices.values().forEach(deviceInfo -> {
+ if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+ toRemove.add(deviceInfo.mDeviceAddress);
+ }
+ });
+ if (toRemove.size() > 0) {
+ final int delay = checkSendBecomingNoisyIntentInt(
+ AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
+ toRemove.stream().forEach(deviceAddress ->
+ // TODO delay not used?
+ makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+ );
+ }
+ }
+ }
+ }
+
+ // must be called before removing the device from mConnectedDevices
+ // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+ // from AudioSystem
+ /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
+ synchronized (mConnectedDevices) {
+ return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
+ }
+ }
+
+ /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+ synchronized (mCurAudioRoutes) {
+ AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
+ mRoutesObservers.register(observer);
+ return routes;
+ }
+ }
+
+ /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+ return mCurAudioRoutes;
+ }
+
+ /*package*/ int setBluetoothA2dpDeviceConnectionState(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
+ int delay;
+ if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
+ throw new IllegalArgumentException("invalid profile " + profile);
+ }
+ synchronized (mConnectedDevices) {
+ if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
+ int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+ delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ intState, musicDevice);
+ } else {
+ delay = 0;
+ }
+
+ final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
+ + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
+ + " suppressNoisyIntent: " + suppressNoisyIntent);
+ }
+
+ final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
+ new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
+ if (profile == BluetoothProfile.A2DP) {
+ mDeviceBroker.postA2dpSinkConnection(state,
+ a2dpDeviceInfo,
+ delay);
+ } else { //profile == BluetoothProfile.A2DP_SINK
+ mDeviceBroker.postA2dpSourceConnection(state,
+ a2dpDeviceInfo,
+ delay);
+ }
+ }
+ return delay;
+ }
+
+ /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+ @NonNull BluetoothDevice device,
+ @AudioService.BtProfileConnectionState int state, int profile,
+ boolean suppressNoisyIntent, int a2dpVolume) {
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ return setBluetoothA2dpDeviceConnectionState(device, state, profile,
+ suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+ }
+ // state == BluetoothProfile.STATE_CONNECTED
+ synchronized (mConnectedDevices) {
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i);
+ if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ continue;
+ }
+ // If A2DP device exists, this is either an active device change or
+ // device config change
+ final String existingDevicekey = mConnectedDevices.keyAt(i);
+ final String deviceName = device.getName();
+ final String address = device.getAddress();
+ final String newDeviceKey = DeviceInfo.makeDeviceListKey(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+ // Device not equal to existing device, active device change
+ if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
+ mConnectedDevices.remove(existingDevicekey);
+ mConnectedDevices.put(newDeviceKey, new DeviceInfo(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
+ address, a2dpCodec));
+ mDeviceBroker.postA2dpActiveDeviceChange(
+ new BtHelper.BluetoothA2dpDeviceInfo(
+ device, a2dpVolume, a2dpCodec));
+ return 0;
+ } else {
+ // Device config change for existing device
+ mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
+ return 0;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+ String address, String name, String caller) {
+ synchronized (mConnectedDevices) {
+ int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
+ mDeviceBroker.postSetWiredDeviceConnectionState(
+ new WiredDeviceConnectionState(type, state, address, name, caller),
+ delay);
+ return delay;
+ }
+ }
+
+ /*package*/ int setBluetoothHearingAidDeviceConnectionState(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ boolean suppressNoisyIntent, int musicDevice) {
+ int delay;
+ synchronized (mConnectedDevices) {
+ if (!suppressNoisyIntent) {
+ int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
+ delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
+ intState, musicDevice);
+ } else {
+ delay = 0;
+ }
+ mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
+ return delay;
+ }
+ }
+
+
+ //-------------------------------------------------------------------
+ // Internal utilities
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
+ int a2dpCodec) {
+ // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
+ // audio policy manager
+ AudioService.VolumeStreamState streamState =
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+ mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+ // Reset A2DP suspend state each time a new sink is connected
+ AudioSystem.setParameters("A2dpSuspended=false");
+ mConnectedDevices.put(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+ new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+ address, a2dpCodec));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ setCurrentAudioRouteNameIfPossible(name);
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+ if (address == null) {
+ return;
+ }
+ mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+ mConnectedDevices.remove(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+ // Remove A2DP routes as well
+ setCurrentAudioRouteNameIfPossible(null);
+ if (mDockAddress == address) {
+ mDockAddress = null;
+ }
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
+ // prevent any activity on the A2DP audio output to avoid unwanted
+ // reconnection of the sink.
+ AudioSystem.setParameters("A2dpSuspended=true");
+ // retrieve DeviceInfo before removing device
+ final String deviceKey =
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+ final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
+ final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
+ AudioSystem.AUDIO_FORMAT_DEFAULT;
+ // the device will be made unavailable later, so consider it disconnected right away
+ mConnectedDevices.remove(deviceKey);
+ // send the delayed message to make the device unavailable later
+ mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+ }
+
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpSrcAvailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.put(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+ new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
+ address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeA2dpSrcUnavailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.remove(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
+ final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC)
+ .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
+ mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC);
+
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.put(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
+ new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
+ address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
+ mDeviceBroker.setDeviceVolume(
+ mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC),
+ AudioSystem.DEVICE_OUT_HEARING_AID);
+ setCurrentAudioRouteNameIfPossible(name);
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void makeHearingAidDeviceUnavailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ mConnectedDevices.remove(
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
+ // Remove Hearing Aid routes as well
+ setCurrentAudioRouteNameIfPossible(null);
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private void setCurrentAudioRouteNameIfPossible(String name) {
+ synchronized (mCurAudioRoutes) {
+ if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
+ return;
+ }
+ if (name != null || !isCurrentDeviceConnected()) {
+ mCurAudioRoutes.bluetoothName = name;
+ mDeviceBroker.postReportNewRoutes();
+ }
+ }
+ }
+
+ @GuardedBy("mConnectedDevices")
+ private boolean isCurrentDeviceConnected() {
+ return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
+ TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
+ }
+
+ // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
+ // sent if:
+ // - none of these devices are connected anymore after one is disconnected AND
+ // - the device being disconnected is actually used for music.
+ // Access synchronized on mConnectedDevices
+ private int mBecomingNoisyIntentDevices =
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
+ | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
+ | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+ | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
+ | AudioSystem.DEVICE_OUT_HEARING_AID;
+
+ // must be called before removing the device from mConnectedDevices
+ // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+ // from AudioSystem
+ @GuardedBy("mConnectedDevices")
+ private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) {
+ if (state != 0) {
+ return 0;
+ }
+ if ((device & mBecomingNoisyIntentDevices) == 0) {
+ return 0;
+ }
+ int delay = 0;
+ int devices = 0;
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ int dev = mConnectedDevices.valueAt(i).mDeviceType;
+ if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
+ && ((dev & mBecomingNoisyIntentDevices) != 0)) {
+ devices |= dev;
+ }
+ }
+ if (musicDevice == AudioSystem.DEVICE_NONE) {
+ musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ }
+ // ignore condition on device being actually used for music when in communication
+ // because music routing is altered in this case.
+ // also checks whether media routing if affected by a dynamic policy
+ if (((device == musicDevice) || mDeviceBroker.isInCommunication())
+ && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) {
+ mDeviceBroker.broadcastBecomingNoisy();
+ delay = 1000;
+ }
+
+ return delay;
+ }
+
+ // Intent "extra" data keys.
+ private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
+ private static final String CONNECT_INTENT_KEY_STATE = "state";
+ private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
+ private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
+ private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
+ private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
+ private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
+
+ private void sendDeviceConnectionIntent(int device, int state, String address,
+ String deviceName) {
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
+ + " state:0x" + Integer.toHexString(state) + " address:" + address
+ + " name:" + deviceName + ");");
+ }
+ Intent intent = new Intent();
+
+ switch(device) {
+ case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 1);
+ break;
+ case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+ case AudioSystem.DEVICE_OUT_LINE:
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 0);
+ break;
+ case AudioSystem.DEVICE_OUT_USB_HEADSET:
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone",
+ AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
+ == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
+ break;
+ case AudioSystem.DEVICE_IN_USB_HEADSET:
+ if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
+ == AudioSystem.DEVICE_STATE_AVAILABLE) {
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 1);
+ } else {
+ // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
+ return;
+ }
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI:
+ case AudioSystem.DEVICE_OUT_HDMI_ARC:
+ configureHdmiPlugIntent(intent, state);
+ break;
+ }
+
+ if (intent.getAction() == null) {
+ return;
+ }
+
+ intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
+ intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
+ intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
+
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void updateAudioRoutes(int device, int state) {
+ int connType = 0;
+
+ switch (device) {
+ case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+ connType = AudioRoutesInfo.MAIN_HEADSET;
+ break;
+ case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+ case AudioSystem.DEVICE_OUT_LINE:
+ connType = AudioRoutesInfo.MAIN_HEADPHONES;
+ break;
+ case AudioSystem.DEVICE_OUT_HDMI:
+ case AudioSystem.DEVICE_OUT_HDMI_ARC:
+ connType = AudioRoutesInfo.MAIN_HDMI;
+ break;
+ case AudioSystem.DEVICE_OUT_USB_DEVICE:
+ case AudioSystem.DEVICE_OUT_USB_HEADSET:
+ connType = AudioRoutesInfo.MAIN_USB;
+ break;
+ }
+
+ synchronized (mCurAudioRoutes) {
+ if (connType == 0) {
+ return;
+ }
+ int newConn = mCurAudioRoutes.mainType;
+ if (state != 0) {
+ newConn |= connType;
+ } else {
+ newConn &= ~connType;
+ }
+ if (newConn != mCurAudioRoutes.mainType) {
+ mCurAudioRoutes.mainType = newConn;
+ mDeviceBroker.postReportNewRoutes();
+ }
+ }
+ }
+
+ private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
+ intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
+ intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
+ if (state != AudioService.CONNECTION_STATE_CONNECTED) {
+ return;
+ }
+ ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+ int[] portGeneration = new int[1];
+ int status = AudioSystem.listAudioPorts(ports, portGeneration);
+ if (status != AudioManager.SUCCESS) {
+ Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
+ return;
+ }
+ for (AudioPort port : ports) {
+ if (!(port instanceof AudioDevicePort)) {
+ continue;
+ }
+ final AudioDevicePort devicePort = (AudioDevicePort) port;
+ if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
+ && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
+ continue;
+ }
+ // found an HDMI port: format the list of supported encodings
+ int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
+ if (formats.length > 0) {
+ ArrayList<Integer> encodingList = new ArrayList(1);
+ for (int format : formats) {
+ // a format in the list can be 0, skip it
+ if (format != AudioFormat.ENCODING_INVALID) {
+ encodingList.add(format);
+ }
+ }
+ final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
+ intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
+ }
+ // find the maximum supported number of channels
+ int maxChannels = 0;
+ for (int mask : devicePort.channelMasks()) {
+ int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
+ if (channelCount > maxChannels) {
+ maxChannels = channelCount;
+ }
+ }
+ intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index de389bc3aa01..df33bf249133 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -35,14 +36,9 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
-import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -65,14 +61,12 @@ import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiTvClient;
import android.hardware.usb.UsbManager;
import android.media.AudioAttributes;
-import android.media.AudioDevicePort;
import android.media.AudioFocusInfo;
import android.media.AudioFocusRequest;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPlaybackConfiguration;
-import android.media.AudioPort;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
@@ -104,7 +98,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -120,8 +113,6 @@ import android.service.notification.ZenModeConfig;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
@@ -137,10 +128,8 @@ import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.audio.AudioServiceEvents.ForceUseEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
-import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -150,6 +139,8 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
@@ -175,19 +166,20 @@ public class AudioService extends IAudioService.Stub
implements AccessibilityManager.TouchExplorationStateChangeListener,
AccessibilityManager.AccessibilityServicesStateChangeListener {
- private static final String TAG = "AudioService";
+ private static final String TAG = "AS.AudioService";
/** Debug audio mode */
- protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
+ protected static final boolean DEBUG_MODE = false;
/** Debug audio policy feature */
- protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
+ protected static final boolean DEBUG_AP = false;
/** Debug volumes */
- protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
+ protected static final boolean DEBUG_VOL = false;
/** debug calls to devices APIs */
- protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG);
+ protected static final boolean DEBUG_DEVICES = false;
+
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -213,11 +205,11 @@ public class AudioService extends IAudioService.Stub
return mPlatformType == AudioSystem.PLATFORM_VOICE;
}
- private boolean isPlatformTelevision() {
+ /*package*/ boolean isPlatformTelevision() {
return mPlatformType == AudioSystem.PLATFORM_TELEVISION;
}
- private boolean isPlatformAutomotive() {
+ /*package*/ boolean isPlatformAutomotive() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
@@ -242,52 +234,40 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_SET_FORCE_USE = 8;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
private static final int MSG_SET_ALL_VOLUMES = 10;
- private static final int MSG_REPORT_NEW_ROUTES = 12;
- private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
- private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
- private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
- private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
- private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
- private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
- private static final int MSG_SYSTEM_READY = 21;
- private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22;
- private static final int MSG_UNMUTE_STREAM = 24;
- private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25;
- private static final int MSG_INDICATE_SYSTEM_READY = 26;
- private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27;
- private static final int MSG_NOTIFY_VOL_EVENT = 28;
- private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29;
- private static final int MSG_ENABLE_SURROUND_FORMATS = 30;
+ private static final int MSG_CHECK_MUSIC_ACTIVE = 11;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13;
+ private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14;
+ private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
+ private static final int MSG_SYSTEM_READY = 16;
+ private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17;
+ private static final int MSG_UNMUTE_STREAM = 18;
+ private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
+ private static final int MSG_INDICATE_SYSTEM_READY = 20;
+ private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21;
+ private static final int MSG_NOTIFY_VOL_EVENT = 22;
+ private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23;
+ private static final int MSG_ENABLE_SURROUND_FORMATS = 24;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
- private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
- private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
- private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
- private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103;
- private static final int MSG_DISABLE_AUDIO_FOR_UID = 104;
- private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105;
- private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106;
- private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107;
+ private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
// end of messages handled under wakelock
- private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
- // Timeout for connection to bluetooth headset service
- private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
-
// retry delay in case of failure to indicate system ready to AudioFlinger
private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000;
- private static final int BT_HEARING_AID_GAIN_MIN = -128;
-
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
/** @see AudioHandler */
private AudioHandler mAudioHandler;
/** @see VolumeStreamState */
private VolumeStreamState[] mStreamStates;
+
+ /*package*/ VolumeStreamState getStreamState(int stream) {
+ return mStreamStates[stream];
+ }
+
private SettingsObserver mSettingsObserver;
private int mMode = AudioSystem.MODE_NORMAL;
@@ -477,135 +457,13 @@ public class AudioService extends IAudioService.Stub
private final UserRestrictionsListener mUserRestrictionsListener =
new AudioServiceUserRestrictionsListener();
- // Devices currently connected
- // Use makeDeviceListKey() to make a unique key for this list.
- private class DeviceListSpec {
- int mDeviceType;
- String mDeviceName;
- String mDeviceAddress;
- int mDeviceCodecFormat;
-
- DeviceListSpec(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat) {
- mDeviceType = deviceType;
- mDeviceName = deviceName;
- mDeviceAddress = deviceAddress;
- mDeviceCodecFormat = deviceCodecFormat;
- }
-
- public String toString() {
- return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName
- + " address:" + mDeviceAddress
- + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
- }
- }
-
- // 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 String makeDeviceListKey(int device, String deviceAddress) {
- return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
- }
-
- private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>();
-
- private class BluetoothA2dpDeviceInfo {
- BluetoothDevice mBtDevice;
- int mVolume;
- int mCodec;
-
- BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) {
- this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
-
- BluetoothA2dpDeviceInfo(BluetoothDevice btDevice,
- int volume, int codec) {
- mBtDevice = btDevice;
- mVolume = volume;
- mCodec = codec;
- }
-
- public BluetoothDevice getBtDevice() {
- return mBtDevice;
- }
-
- public int getVolume() {
- return mVolume;
- }
-
- public int getCodec() {
- return mCodec;
- }
- }
-
- private int mapBluetoothCodecToAudioFormat(int btCodecType) {
- switch (btCodecType) {
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
- return AudioSystem.AUDIO_FORMAT_SBC;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
- return AudioSystem.AUDIO_FORMAT_AAC;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
- return AudioSystem.AUDIO_FORMAT_APTX;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
- return AudioSystem.AUDIO_FORMAT_APTX_HD;
- case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
- return AudioSystem.AUDIO_FORMAT_LDAC;
- default:
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- }
-
- // Forced device usage for communications
- private int mForcedUseForComm;
- private int mForcedUseForCommExt; // External state returned by getters: always consistent
- // with requests by setters
-
// List of binder death handlers for setMode() client processes.
// The last process to have called setMode() is at the top of the list.
- private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
-
- // List of clients having issued a SCO start request
- private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
-
- // BluetoothHeadset API to control SCO connection
- private BluetoothHeadset mBluetoothHeadset;
-
- // Bluetooth headset device
- private BluetoothDevice mBluetoothHeadsetDevice;
-
- // Indicate if SCO audio connection is currently active and if the initiator is
- // audio service (internal) or bluetooth headset (external)
- private int mScoAudioState;
- // SCO audio state is not active
- private static final int SCO_STATE_INACTIVE = 0;
- // SCO audio activation request waiting for headset service to connect
- private static final int SCO_STATE_ACTIVATE_REQ = 1;
- // SCO audio state is active or starting due to a request from AudioManager API
- private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
- // SCO audio deactivation request waiting for headset service to connect
- private static final int SCO_STATE_DEACTIVATE_REQ = 4;
- // SCO audio deactivation in progress, waiting for Bluetooth audio intent
- private static final int SCO_STATE_DEACTIVATING = 5;
-
- // SCO audio state is active due to an action in BT handsfree (either voice recognition or
- // in call audio)
- private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
-
- // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
- // originated from an app targeting an API version before JB MR2 and raw audio after that.
- private int mScoAudioMode;
- // SCO audio mode is undefined
- private static final int SCO_MODE_UNDEFINED = -1;
- // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
- private static final int SCO_MODE_VIRTUAL_CALL = 0;
- // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
- private static final int SCO_MODE_RAW = 1;
- // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
- private static final int SCO_MODE_VR = 2;
-
- private static final int SCO_MODE_MAX = 2;
-
- // Current connection state indicated by bluetooth headset
- private int mScoConnectionState;
+ // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
+ //TODO candidate to be moved to separate class that handles synchronization
+ @GuardedBy("mDeviceBroker.mSetModeLock")
+ /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+ new ArrayList<SetModeDeathHandler>();
// true if boot sequence has been completed
private boolean mSystemReady;
@@ -636,15 +494,6 @@ public class AudioService extends IAudioService.Stub
// Used to play ringtones outside system_server
private volatile IRingtonePlayer mRingtonePlayer;
- // Request to override default use of A2DP for media.
- private boolean mBluetoothA2dpEnabled;
- private final Object mBluetoothA2dpEnabledLock = new Object();
-
- // Monitoring of audio routes. Protected by mCurAudioRoutes.
- final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
- final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
- = new RemoteCallbackList<IAudioRoutesObserver>();
-
// Devices for which the volume is fixed and VolumePanel slider should be disabled
int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
@@ -669,17 +518,6 @@ public class AudioService extends IAudioService.Stub
private final MediaFocusControl mMediaFocusControl;
- // Reference to BluetoothA2dp to query for volume.
- private BluetoothHearingAid mHearingAid;
- // lock always taken synchronized on mConnectedDevices
- private final Object mHearingAidLock = new Object();
- // Reference to BluetoothA2dp to query for AbsoluteVolume.
- private BluetoothA2dp mA2dp;
- // lock always taken synchronized on mConnectedDevices
- private final Object mA2dpAvrcpLock = new Object();
- // If absolute volume is supported in AVRCP device
- private boolean mAvrcpAbsVolSupported = false;
-
// Pre-scale for Bluetooth Absolute Volume
private float[] mPrescaleAbsoluteVolume = new float[] {
0.5f, // Pre-scale for index 1
@@ -687,8 +525,6 @@ public class AudioService extends IAudioService.Stub
0.85f, // Pre-scale for index 3
};
- private static Long mLastDeviceConnectMsgTime = new Long(0);
-
private NotificationManager mNm;
private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
@@ -705,15 +541,6 @@ public class AudioService extends IAudioService.Stub
@GuardedBy("mSettingsLock")
private int mAssistantUid;
- // Intent "extra" data keys.
- public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
- public static final String CONNECT_INTENT_KEY_STATE = "state";
- public static final String CONNECT_INTENT_KEY_ADDRESS = "address";
- public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
- public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
- public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
- public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
-
// Defines the format for the connection "address" for ALSA devices
public static String makeAlsaAddressString(int card, int device) {
return "card=" + card + ";device=" + device + ";";
@@ -858,8 +685,6 @@ public class AudioService extends IAudioService.Stub
sSoundEffectVolumeDb = context.getResources().getInteger(
com.android.internal.R.integer.config_soundEffectVolumeDb);
- mForcedUseForComm = AudioSystem.FORCE_NONE;
-
createAudioSystemThread();
AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -886,6 +711,8 @@ public class AudioService extends IAudioService.Stub
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this);
+
// must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
// array initialized by updateStreamVolumeAlias()
updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
@@ -988,23 +815,7 @@ public class AudioService extends IAudioService.Stub
sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
0, 0, null, 0);
- mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
- resetBluetoothSco();
- getBluetoothHeadset();
- //FIXME: this is to maintain compatibility with deprecated intent
- // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- sendStickyBroadcastToAll(newIntent);
-
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.A2DP);
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEARING_AID);
- }
+ mDeviceBroker.onSystemReady();
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
synchronized (mHdmiClientLock) {
@@ -1065,39 +876,22 @@ public class AudioService extends IAudioService.Stub
readAndSetLowRamDevice();
- // Restore device connection states
- synchronized (mConnectedDevices) {
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec spec = mConnectedDevices.valueAt(i);
- AudioSystem.setDeviceConnectionState(
- spec.mDeviceType,
- AudioSystem.DEVICE_STATE_AVAILABLE,
- spec.mDeviceAddress,
- spec.mDeviceName,
- spec.mDeviceCodecFormat);
- }
- }
+ // Restore device connection states, BT state
+ mDeviceBroker.onAudioServerDied();
+
// Restore call state
if (AudioSystem.setPhoneState(mMode) == AudioSystem.AUDIO_STATUS_OK) {
mModeLogger.log(new AudioEventLogger.StringEvent(
"onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")"));
}
- // Restore forced usage for communications and record
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm);
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
final int forSys;
synchronized (mSettingsLock) {
forSys = mCameraSoundForced ?
AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
}
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
+
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied");
// Restore stream volumes
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1120,20 +914,10 @@ public class AudioService extends IAudioService.Stub
RotationHelper.updateOrientation();
}
- synchronized (mBluetoothA2dpEnabledLock) {
- final int forMed = mBluetoothA2dpEnabled ?
- AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed);
- }
-
synchronized (mSettingsLock) {
final int forDock = mDockAudioMediaEnabled ?
AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock,
- "onAudioServerDied"));
- AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock);
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
sendEnabledSurroundFormats(mContentResolver, true);
updateAssistantUId(true);
@@ -1209,6 +993,45 @@ public class AudioService extends IAudioService.Stub
}
}
+ /**
+ * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected.
+ */
+ /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+ if (state != 0) {
+ // DEVICE_OUT_HDMI is now connected
+ if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ }
+
+ if (isPlatformTelevision()) {
+ mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+ checkAllFixedVolumeDevices();
+ synchronized (mHdmiClientLock) {
+ if (mHdmiManager != null && mHdmiPlaybackClient != null) {
+ mHdmiCecSink = false;
+ mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+ }
+ }
+ }
+ sendEnabledSurroundFormats(mContentResolver, true);
+ } else {
+ // DEVICE_OUT_HDMI disconnected
+ if (isPlatformTelevision()) {
+ synchronized (mHdmiClientLock) {
+ if (mHdmiManager != null) {
+ mHdmiCecSink = false;
+ }
+ }
+ }
+ }
+ }
+
private void checkAllFixedVolumeDevices()
{
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1373,7 +1196,7 @@ public class AudioService extends IAudioService.Stub
private void sendEncodedSurroundMode(ContentResolver cr, String eventSource)
{
- int encodedSurroundMode = Settings.Global.getInt(
+ final int encodedSurroundMode = Settings.Global.getInt(
cr, Settings.Global.ENCODED_SURROUND_OUTPUT,
Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
sendEncodedSurroundMode(encodedSurroundMode, eventSource);
@@ -1402,13 +1225,8 @@ public class AudioService extends IAudioService.Stub
break;
}
if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) {
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_ENCODED_SURROUND,
- forceSetting,
- eventSource,
- 0);
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting,
+ eventSource);
}
}
@@ -1632,7 +1450,7 @@ public class AudioService extends IAudioService.Stub
+ ", flags=" + flags + ", caller=" + caller
+ ", volControlStream=" + mVolumeControlStream
+ ", userSelect=" + mUserSelectedVolumeControlStream);
- mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
.append("/").append(caller).append(" uid:").append(uid).toString()));
final int streamType;
@@ -1690,7 +1508,7 @@ public class AudioService extends IAudioService.Stub
+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
return;
}
- mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
Binder.getCallingUid());
@@ -1871,16 +1689,18 @@ public class AudioService extends IAudioService.Stub
if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
(device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
- }
+ if (DEBUG_VOL) {
+ Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+ + newIndex + "stream=" + streamType);
}
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex);
}
// Check if volume update should be send to Hearing Aid
if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
- setHearingAidVolume(newIndex, streamType);
+ Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex
+ + " stream=" + streamType);
+ mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
}
// Check if volume update should be sent to Hdmi system audio.
@@ -2052,7 +1872,7 @@ public class AudioService extends IAudioService.Stub
+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);
return;
}
- mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage));
setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
@@ -2127,18 +1947,20 @@ public class AudioService extends IAudioService.Stub
index = rescaleIndex(index * 10, streamType, streamTypeAlias);
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
- (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp != null && mAvrcpAbsVolSupported) {
- mA2dp.setAvrcpAbsoluteVolume(index / 10);
- }
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0
+ && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
+ + "stream=" + streamType);
}
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
}
if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
- setHearingAidVolume(index, streamType);
+ Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+ + " stream=" + streamType);
+ mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
}
if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
@@ -2881,6 +2703,10 @@ public class AudioService extends IAudioService.Stub
}
}
+ /*package*/ void setUpdateRingerModeServiceInt() {
+ setRingerModeInt(getRingerModeInternal(), false);
+ }
+
/** @see AudioManager#shouldVibrate(int) */
public boolean shouldVibrate(int vibrateType) {
if (!mHasVibrator) return false;
@@ -2921,7 +2747,7 @@ public class AudioService extends IAudioService.Stub
}
- private class SetModeDeathHandler implements IBinder.DeathRecipient {
+ /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient {
private IBinder mCb; // To be notified of client's death
private int mPid;
private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
@@ -2934,7 +2760,7 @@ public class AudioService extends IAudioService.Stub
public void binderDied() {
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
+ synchronized (mDeviceBroker.mSetModeLock) {
Log.w(TAG, "setMode() client died");
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -2949,9 +2775,7 @@ public class AudioService extends IAudioService.Stub
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
- final long ident = Binder.clearCallingIdentity();
- disconnectBluetoothSco(newModeOwnerPid);
- Binder.restoreCallingIdentity(ident);
+ mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
}
}
@@ -2994,7 +2818,7 @@ public class AudioService extends IAudioService.Stub
int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
- synchronized(mSetModeDeathHandlers) {
+ synchronized (mDeviceBroker.mSetModeLock) {
if (!mSetModeDeathHandlers.isEmpty()) {
oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
@@ -3006,11 +2830,11 @@ public class AudioService extends IAudioService.Stub
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
// SCO connections not started by the application changing the mode when pid changes
if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
- disconnectBluetoothSco(newModeOwnerPid);
+ mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
}
}
- // must be called synchronized on mSetModeDeathHandlers
+ // must be called synchronized on mSetModeLock
// setModeInt() returns a valid PID if the audio mode was successfully set to
// any mode other than NORMAL.
private int setModeInt(int mode, IBinder cb, int pid, String caller) {
@@ -3380,26 +3204,12 @@ public class AudioService extends IAudioService.Stub
final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
-
- if (on) {
- if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE,
- eventSource, 0);
- }
- mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
- } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
-
- mForcedUseForCommExt = mForcedUseForComm;
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
+ mDeviceBroker.setSpeakerphoneOn(on, eventSource);
}
/** @see AudioManager#isSpeakerphoneOn() */
public boolean isSpeakerphoneOn() {
- return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+ return mDeviceBroker.isSpeakerphoneOn();
}
/** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -3410,7 +3220,7 @@ public class AudioService extends IAudioService.Stub
// Only enable calls from system components
if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
- mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+ mDeviceBroker.setBluetoothScoOnByApp(on);
return;
}
@@ -3418,95 +3228,57 @@ public class AudioService extends IAudioService.Stub
final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
- setBluetoothScoOnInt(on, eventSource);
- }
-
- public void setBluetoothScoOnInt(boolean on, String eventSource) {
- Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
- if (on) {
- // do not accept SCO ON if SCO audio is not connected
- synchronized (mScoClients) {
- if ((mBluetoothHeadset != null)
- && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
- mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
- Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
- + mBluetoothHeadsetDevice + " is not in audio connected mode");
- return;
- }
- }
- mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
- } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
- }
- mForcedUseForCommExt = mForcedUseForComm;
- AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
- sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0);
- // Un-mute ringtone stream volume
- setRingerModeInt(getRingerModeInternal(), false);
+
+ mDeviceBroker.setBluetoothScoOn(on, eventSource);
}
- /** @see AudioManager#isBluetoothScoOn() */
+ /** @see AudioManager#isBluetoothScoOn()
+ * Note that it doesn't report internal state, but state seen by apps (which may have
+ * called setBluetoothScoOn() */
public boolean isBluetoothScoOn() {
- return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
+ return mDeviceBroker.isBluetoothScoOnForApp();
}
+ // TODO investigate internal users due to deprecation of SDK API
/** @see AudioManager#setBluetoothA2dpOn(boolean) */
public void setBluetoothA2dpOn(boolean on) {
// for logging only
final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
.append(Binder.getCallingPid()).toString();
-
- synchronized (mBluetoothA2dpEnabledLock) {
- if (mBluetoothA2dpEnabled == on) {
- return;
- }
- mBluetoothA2dpEnabled = on;
- sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- eventSource, 0);
- }
+ mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource);
}
/** @see AudioManager#isBluetoothA2dpOn() */
public boolean isBluetoothA2dpOn() {
- synchronized (mBluetoothA2dpEnabledLock) {
- return mBluetoothA2dpEnabled;
- }
+ return mDeviceBroker.isBluetoothA2dpOn();
}
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
- int scoAudioMode =
+ final int scoAudioMode =
(targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
- SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
- startBluetoothScoInt(cb, scoAudioMode);
+ BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
+ final String eventSource = new StringBuilder("startBluetoothSco()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ startBluetoothScoInt(cb, scoAudioMode, eventSource);
}
/** @see AudioManager#startBluetoothScoVirtualCall() */
public void startBluetoothScoVirtualCall(IBinder cb) {
- startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+ final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
}
- void startBluetoothScoInt(IBinder cb, int scoAudioMode){
+ void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
if (!checkAudioSettingsPermission("startBluetoothSco()") ||
!mSystemReady) {
return;
}
- ScoClient client = getScoClient(cb, true);
- // The calling identity must be cleared before calling ScoClient.incCount().
- // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- // The caller identity must be cleared after getScoClient() because it is needed if a new
- // client is created.
- final long ident = Binder.clearCallingIdentity();
- client.incCount(scoAudioMode);
- Binder.restoreCallingIdentity(ident);
+ mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
}
/** @see AudioManager#stopBluetoothSco() */
@@ -3515,648 +3287,15 @@ public class AudioService extends IAudioService.Stub
!mSystemReady) {
return;
}
- ScoClient client = getScoClient(cb, false);
- // The calling identity must be cleared before calling ScoClient.decCount().
- // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- final long ident = Binder.clearCallingIdentity();
- if (client != null) {
- client.decCount();
- }
- Binder.restoreCallingIdentity(ident);
- }
-
-
- private class ScoClient implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
- private int mCreatorPid;
- private int mStartcount; // number of SCO connections started by this client
-
- ScoClient(IBinder cb) {
- mCb = cb;
- mCreatorPid = Binder.getCallingPid();
- mStartcount = 0;
- }
-
- public void binderDied() {
- synchronized(mScoClients) {
- Log.w(TAG, "SCO client died");
- int index = mScoClients.indexOf(this);
- if (index < 0) {
- Log.w(TAG, "unregistered SCO client died");
- } else {
- clearCount(true);
- mScoClients.remove(this);
- }
- }
- }
-
- public void incCount(int scoAudioMode) {
- synchronized(mScoClients) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
- if (mStartcount == 0) {
- try {
- mCb.linkToDeath(this, 0);
- } catch (RemoteException e) {
- // client has already died!
- Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death");
- }
- }
- mStartcount++;
- }
- }
-
- public void decCount() {
- synchronized(mScoClients) {
- if (mStartcount == 0) {
- Log.w(TAG, "ScoClient.decCount() already 0");
- } else {
- mStartcount--;
- if (mStartcount == 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "decCount() going to 0 but not registered to binder");
- }
- }
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
- }
-
- public void clearCount(boolean stopSco) {
- synchronized(mScoClients) {
- if (mStartcount != 0) {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder");
- }
- }
- mStartcount = 0;
- if (stopSco) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
- }
- }
- }
-
- public int getCount() {
- return mStartcount;
- }
-
- public IBinder getBinder() {
- return mCb;
- }
-
- public int getPid() {
- return mCreatorPid;
- }
-
- public int totalCount() {
- synchronized(mScoClients) {
- int count = 0;
- for (ScoClient mScoClient : mScoClients) {
- count += mScoClient.getCount();
- }
- return count;
- }
- }
-
- private void requestScoState(int state, int scoAudioMode) {
- checkScoAudioState();
- int clientCount = totalCount();
- if (clientCount != 0) {
- Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
- + ", clientCount=" + clientCount);
- return;
- }
- if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- // Make sure that the state transitions to CONNECTING even if we cannot initiate
- // the connection.
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client process.
- synchronized(mSetModeDeathHandlers) {
- int modeOwnerPid = mSetModeDeathHandlers.isEmpty()
- ? 0 : mSetModeDeathHandlers.get(0).getPid();
- if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
- Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
- + modeOwnerPid + " != creatorPid " + mCreatorPid);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return;
- }
- switch (mScoAudioState) {
- case SCO_STATE_INACTIVE:
- mScoAudioMode = scoAudioMode;
- if (scoAudioMode == SCO_MODE_UNDEFINED) {
- mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- if (mBluetoothHeadsetDevice != null) {
- mScoAudioMode = Settings.Global.getInt(mContentResolver,
- "bluetooth_sco_channel_"
- + mBluetoothHeadsetDevice.getAddress(),
- SCO_MODE_VIRTUAL_CALL);
- if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
- mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- }
- }
- }
- if (mBluetoothHeadset == null) {
- if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- } else {
- Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
- + " connection, mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- }
- if (mBluetoothHeadsetDevice == null) {
- Log.w(TAG, "requestScoState: no active device while connecting,"
- + " mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- } else {
- Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
- + " failed, mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- case SCO_STATE_DEACTIVATING:
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
- break;
- default:
- Log.w(TAG, "requestScoState: failed to connect in state "
- + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
-
- }
- }
- } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- switch (mScoAudioState) {
- case SCO_STATE_ACTIVE_INTERNAL:
- if (mBluetoothHeadset == null) {
- if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
- } else {
- Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
- + " disconnection, mScoAudioMode=" + mScoAudioMode);
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- }
- if (mBluetoothHeadsetDevice == null) {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_DEACTIVATING;
- } else {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- break;
- case SCO_STATE_ACTIVATE_REQ:
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- default:
- Log.w(TAG, "requestScoState: failed to disconnect in state "
- + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- }
- }
- }
-
- private void checkScoAudioState() {
- synchronized (mScoClients) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
- mScoAudioState == SCO_STATE_INACTIVE &&
- mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- }
- }
-
-
- private ScoClient getScoClient(IBinder cb, boolean create) {
- synchronized(mScoClients) {
- for (ScoClient existingClient : mScoClients) {
- if (existingClient.getBinder() == cb) {
- return existingClient;
- }
- }
- if (create) {
- ScoClient newClient = new ScoClient(cb);
- mScoClients.add(newClient);
- return newClient;
- }
- return null;
- }
- }
-
- public void clearAllScoClients(int exceptPid, boolean stopSco) {
- synchronized(mScoClients) {
- ScoClient savedClient = null;
- for (ScoClient cl : mScoClients) {
- if (cl.getPid() != exceptPid) {
- cl.clearCount(stopSco);
- } else {
- savedClient = cl;
- }
- }
- mScoClients.clear();
- if (savedClient != null) {
- mScoClients.add(savedClient);
- }
- }
- }
-
- private boolean getBluetoothHeadset() {
- boolean result = false;
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
- // If we could not get a bluetooth headset proxy, send a failure message
- // without delay to reset the SCO audio state and clear SCO clients.
- // If we could get a proxy, send a delayed failure message that will reset our state
- // in case we don't receive onServiceConnected().
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
- return result;
- }
-
- /**
- * Disconnect all SCO connections started by {@link AudioManager} except those started by
- * {@param exceptPid}
- *
- * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
- */
- private void disconnectBluetoothSco(int exceptPid) {
- synchronized(mScoClients) {
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- return;
- }
- clearAllScoClients(exceptPid, true);
- }
- }
-
- private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
- BluetoothDevice device, int scoAudioMode) {
- switch (scoAudioMode) {
- case SCO_MODE_RAW:
- return bluetoothHeadset.disconnectAudio();
- case SCO_MODE_VIRTUAL_CALL:
- return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
- case SCO_MODE_VR:
- return bluetoothHeadset.stopVoiceRecognition(device);
- default:
- return false;
- }
- }
-
- private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
- BluetoothDevice device, int scoAudioMode) {
- switch (scoAudioMode) {
- case SCO_MODE_RAW:
- return bluetoothHeadset.connectAudio();
- case SCO_MODE_VIRTUAL_CALL:
- return bluetoothHeadset.startScoUsingVirtualVoiceCall();
- case SCO_MODE_VR:
- return bluetoothHeadset.startVoiceRecognition(device);
- default:
- return false;
- }
- }
-
- private void resetBluetoothSco() {
- synchronized(mScoClients) {
- clearAllScoClients(0, false);
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- AudioSystem.setParameters("A2dpSuspended=false");
- setBluetoothScoOnInt(false, "resetBluetoothSco");
- }
-
- private void broadcastScoConnectionState(int state) {
- sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE,
- SENDMSG_QUEUE, state, 0, null, 0);
- }
-
- private void onBroadcastScoConnectionState(int state) {
- if (state != mScoConnectionState) {
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
- mScoConnectionState);
- sendStickyBroadcastToAll(newIntent);
- mScoConnectionState = state;
- }
- }
-
- private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
- if (btDevice == null) {
- return true;
- }
- String address = btDevice.getAddress();
- BluetoothClass btClass = btDevice.getBluetoothClass();
- int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
- int[] outDeviceTypes = {
- AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
- AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
- AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
- };
- if (btClass != null) {
- switch (btClass.getDeviceClass()) {
- case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
- case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
- outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
- break;
- case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
- outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
- break;
- }
- }
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
- String btDeviceName = btDevice.getName();
- boolean result = false;
- if (isActive) {
- result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName);
- } else {
- for (int outDeviceType : outDeviceTypes) {
- result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName);
- }
- }
- // handleDeviceConnection() && result to make sure the method get executed
- result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
- return result;
- }
-
- private void setBtScoActiveDevice(BluetoothDevice btDevice) {
- synchronized (mScoClients) {
- Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
- final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
- if (!Objects.equals(btDevice, previousActiveDevice)) {
- if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
- Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
- + previousActiveDevice);
- }
- if (!handleBtScoActiveDeviceChange(btDevice, true)) {
- Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
- // set mBluetoothHeadsetDevice to null when failing to add new device
- btDevice = null;
- }
- mBluetoothHeadsetDevice = btDevice;
- if (mBluetoothHeadsetDevice == null) {
- resetBluetoothSco();
- }
- }
- }
- }
-
- private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- BluetoothDevice btDevice;
- List<BluetoothDevice> deviceList;
- switch(profile) {
- case BluetoothProfile.A2DP:
- synchronized (mConnectedDevices) {
- synchronized (mA2dpAvrcpLock) {
- mA2dp = (BluetoothA2dp) proxy;
- deviceList = mA2dp.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- int state = mA2dp.getConnectionState(btDevice);
- int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
- int delay = checkSendBecomingNoisyIntent(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
- AudioSystem.DEVICE_NONE);
- final String addr = btDevice == null ? "null" : btDevice.getAddress();
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2DP service connected: device addr=" + addr
- + " state=" + state));
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_A2DP_SINK_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- new BluetoothA2dpDeviceInfo(btDevice),
- delay);
- }
- }
- }
- break;
-
- case BluetoothProfile.A2DP_SINK:
- deviceList = proxy.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- synchronized (mConnectedDevices) {
- int state = proxy.getConnectionState(btDevice);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_A2DP_SRC_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- new BluetoothA2dpDeviceInfo(btDevice),
- 0 /* delay */);
- }
- }
- break;
-
- case BluetoothProfile.HEADSET:
- synchronized (mScoClients) {
- // Discard timeout message
- mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
- // Refresh SCO audio state
- checkScoAudioState();
- // Continue pending action if any
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
- boolean status = false;
- if (mBluetoothHeadsetDevice != null) {
- switch (mScoAudioState) {
- case SCO_STATE_ACTIVATE_REQ:
- status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode);
- if (status) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- }
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode);
- if (status) {
- mScoAudioState = SCO_STATE_DEACTIVATING;
- }
- break;
- }
- }
- if (!status) {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- }
- }
- break;
-
- case BluetoothProfile.HEARING_AID:
- synchronized (mConnectedDevices) {
- synchronized (mHearingAidLock) {
- mHearingAid = (BluetoothHearingAid) proxy;
- deviceList = mHearingAid.getConnectedDevices();
- if (deviceList.size() > 0) {
- btDevice = deviceList.get(0);
- int state = mHearingAid.getConnectionState(btDevice);
- int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
- int delay = checkSendBecomingNoisyIntent(
- AudioSystem.DEVICE_OUT_HEARING_AID, intState,
- AudioSystem.DEVICE_NONE);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_HEARING_AID_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- btDevice,
- delay);
- }
- }
- }
-
- break;
-
- default:
- break;
- }
- }
- public void onServiceDisconnected(int profile) {
-
- switch (profile) {
- case BluetoothProfile.A2DP:
- disconnectA2dp();
- break;
-
- case BluetoothProfile.A2DP_SINK:
- disconnectA2dpSink();
- break;
-
- case BluetoothProfile.HEADSET:
- disconnectHeadset();
- break;
-
- case BluetoothProfile.HEARING_AID:
- disconnectHearingAid();
- break;
-
- default:
- break;
- }
- }
- };
-
- void disconnectAllBluetoothProfiles() {
- disconnectA2dp();
- disconnectA2dpSink();
- disconnectHeadset();
- disconnectHearingAid();
- }
-
- void disconnectA2dp() {
- synchronized (mConnectedDevices) {
- synchronized (mA2dpAvrcpLock) {
- ArraySet<String> toRemove = null;
- // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- toRemove = toRemove != null ? toRemove : new ArraySet<String>();
- toRemove.add(deviceSpec.mDeviceAddress);
- }
- }
- if (toRemove != null) {
- int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- 0, AudioSystem.DEVICE_NONE);
- for (int i = 0; i < toRemove.size(); i++) {
- makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay);
- }
- }
- }
- }
- }
-
- void disconnectA2dpSink() {
- synchronized (mConnectedDevices) {
- ArraySet<String> toRemove = null;
- // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
- for(int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
- toRemove = toRemove != null ? toRemove : new ArraySet<String>();
- toRemove.add(deviceSpec.mDeviceAddress);
- }
- }
- if (toRemove != null) {
- for (int i = 0; i < toRemove.size(); i++) {
- makeA2dpSrcUnavailable(toRemove.valueAt(i));
- }
- }
- }
+ final String eventSource = new StringBuilder("stopBluetoothSco()")
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
}
- void disconnectHeadset() {
- synchronized (mScoClients) {
- setBtScoActiveDevice(null);
- mBluetoothHeadset = null;
- }
- }
- void disconnectHearingAid() {
- synchronized (mConnectedDevices) {
- synchronized (mHearingAidLock) {
- ArraySet<String> toRemove = null;
- // Disconnect ALL DEVICE_OUT_HEARING_AID devices
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
- toRemove = toRemove != null ? toRemove : new ArraySet<String>();
- toRemove.add(deviceSpec.mDeviceAddress);
- }
- }
- if (toRemove != null) {
- int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
- 0, AudioSystem.DEVICE_NONE);
- for (int i = 0; i < toRemove.size(); i++) {
- makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/);
- }
- }
- }
- }
+ /*package*/ ContentResolver getContentResolver() {
+ return mContentResolver;
}
private void onCheckMusicActive(String caller) {
@@ -4173,8 +3312,8 @@ public class AudioService extends IAudioService.Stub
caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
- (index > safeMediaVolumeIndex(device))) {
+ if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
+ && (index > safeMediaVolumeIndex(device))) {
// Approximate cumulative active music time
mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
@@ -4192,8 +3331,7 @@ public class AudioService extends IAudioService.Stub
mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
}
- private int getSafeUsbMediaVolumeIndex()
- {
+ private int getSafeUsbMediaVolumeIndex() {
// determine UI volume index corresponding to the wanted safe gain in dBFS
int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -4201,7 +3339,7 @@ public class AudioService extends IAudioService.Stub
mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
- while (Math.abs(max-min) > 1) {
+ while (Math.abs(max - min) > 1) {
int index = (max + min) / 2;
float gainDB = AudioSystem.getStreamVolumeDB(
AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
@@ -4518,7 +3656,7 @@ public class AudioService extends IAudioService.Stub
|| adjust == AudioManager.ADJUST_TOGGLE_MUTE;
}
- private boolean isInCommunication() {
+ /*package*/ boolean isInCommunication() {
boolean IsInCall = false;
TelecomManager telecomManager =
@@ -4671,25 +3809,9 @@ public class AudioService extends IAudioService.Stub
} else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
return;
}
- synchronized (mLastDeviceConnectMsgTime) {
- long time = SystemClock.uptimeMillis() + delay;
-
- if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
- msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
- msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
- msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
- msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
- msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE ||
- msg == MSG_BTA2DP_DOCK_TIMEOUT) {
- if (mLastDeviceConnectMsgTime >= time) {
- // add a little delay to make sure messages are ordered as expected
- time = mLastDeviceConnectMsgTime + 30;
- }
- mLastDeviceConnectMsgTime = time;
- }
- handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
- }
+ final long time = SystemClock.uptimeMillis() + delay;
+ handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
}
boolean checkAudioSettingsPermission(String method) {
@@ -4704,7 +3826,7 @@ public class AudioService extends IAudioService.Stub
return false;
}
- private int getDeviceForStream(int stream) {
+ /*package*/ int getDeviceForStream(int stream) {
int device = getDevicesForStream(stream);
if ((device & (device - 1)) != 0) {
// Multiple device selection is either:
@@ -4749,160 +3871,94 @@ public class AudioService extends IAudioService.Stub
}
}
- private int getA2dpCodec(BluetoothDevice device) {
- synchronized (mA2dpAvrcpLock) {
- if (mA2dp == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
- if (btCodecStatus == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
- if (btCodecConfig == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
- }
- return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
- }
+
+ /*package*/ void observeDevicesForAllStreams() {
+ observeDevicesForStreams(-1);
}
- /*
- * A class just for packaging up a set of connection parameters.
+ /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
+ /*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
+ /**
+ * The states that can be used with AudioService.setWiredDeviceConnectionState()
+ * Attention: those values differ from those in BluetoothProfile, follow annotations to
+ * distinguish between @ConnectionState and @BtProfileConnectionState
*/
- class WiredDeviceConnectionState {
- public final int mType;
- public final int mState;
- public final String mAddress;
- public final String mName;
- public final String mCaller;
+ @IntDef({
+ CONNECTION_STATE_DISCONNECTED,
+ CONNECTION_STATE_CONNECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ConnectionState {}
- public WiredDeviceConnectionState(int type, int state, String address, String name,
- String caller) {
- mType = type;
- mState = state;
- mAddress = address;
- mName = name;
- mCaller = caller;
- }
- }
-
- public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+ /**
+ * see AudioManager.setWiredDeviceConnectionState()
+ */
+ public void setWiredDeviceConnectionState(int type,
+ @ConnectionState int state, String address, String name,
String caller) {
- synchronized (mConnectedDevices) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
- + address + ")");
- }
- int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
- 0 /* arg1 unused */,
- 0 /* arg2 unused */,
- new WiredDeviceConnectionState(type, state, address, name, caller),
- delay);
+ if (state != CONNECTION_STATE_CONNECTED
+ && state != CONNECTION_STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Invalid state " + state);
}
+ mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
}
+ /**
+ * @hide
+ * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
+ * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+ */
+ @IntDef({
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BtProfileConnectionState {}
+
public int setBluetoothHearingAidDeviceConnectionState(
- BluetoothDevice device, int state, boolean suppressNoisyIntent,
- int musicDevice)
+ @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
+ boolean suppressNoisyIntent, int musicDevice)
{
- int delay;
- mDeviceLogger.log((new AudioEventLogger.StringEvent(
- "setHearingAidDeviceConnectionState state=" + state
- + " addr=" + device.getAddress()
- + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG));
- synchronized (mConnectedDevices) {
- if (!suppressNoisyIntent) {
- int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
- delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
- intState, musicDevice);
- } else {
- delay = 0;
- }
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_SET_HEARING_AID_CONNECTION_STATE,
- state,
- 0 /* arg2 unused */,
- device,
- delay);
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
}
- return delay;
- }
-
- public int setBluetoothA2dpDeviceConnectionState(
- BluetoothDevice device, int state, int profile)
- {
- return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- device, state, profile, false /* suppressNoisyIntent */,
- -1 /* a2dpVolume */);
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+ + " (dis)connection, got " + state);
+ }
+ return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+ device, state, suppressNoisyIntent, musicDevice, "AudioService");
}
- public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device,
- int state, int profile, boolean suppressNoisyIntent, int a2dpVolume)
- {
- mDeviceLogger.log((new AudioEventLogger.StringEvent(
- "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
- // only querying address as this is the only readily available field on the device
- + " addr=" + device.getAddress()
- + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
- + " vol=" + a2dpVolume)).printLog(TAG));
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) {
- mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored"));
- return 0;
- }
- return setBluetoothA2dpDeviceConnectionStateInt(
- device, state, profile, suppressNoisyIntent,
- AudioSystem.DEVICE_NONE, a2dpVolume);
- }
-
- public int setBluetoothA2dpDeviceConnectionStateInt(
- BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
- int musicDevice, int a2dpVolume)
- {
- int delay;
- if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
- throw new IllegalArgumentException("invalid profile " + profile);
+ /**
+ * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+ */
+ public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
}
- synchronized (mConnectedDevices) {
- if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
- int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
- delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- intState, musicDevice);
- } else {
- delay = 0;
- }
-
- int a2dpCodec = getA2dpCodec(device);
-
- if (DEBUG_DEVICES) {
- Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device
- + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
- + " suppressNoisyIntent: " + suppressNoisyIntent);
- }
-
- queueMsgUnderWakeLock(mAudioHandler,
- (profile == BluetoothProfile.A2DP ?
- MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
- state,
- 0, /* arg2 unused */
- new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec),
- delay);
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+ + " (dis)connection, got " + state);
}
- return delay;
+ return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
+ profile, suppressNoisyIntent, a2dpVolume);
}
+ /**
+ * See AudioManager.handleBluetoothA2dpDeviceConfigChange()
+ * @param device
+ */
public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device)
{
- synchronized (mConnectedDevices) {
- int a2dpCodec = getA2dpCodec(device);
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_A2DP_DEVICE_CONFIG_CHANGE,
- 0 /* arg1 unused */,
- 0 /* arg2 unused */,
- new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec),
- 0 /* delay */);
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
}
+ mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
}
/**
@@ -4912,52 +3968,18 @@ public class AudioService extends IAudioService.Stub
public int handleBluetoothA2dpActiveDeviceChange(
BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
int a2dpVolume) {
+ if (device == null) {
+ throw new IllegalArgumentException("Illegal null device");
+ }
if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
throw new IllegalArgumentException("invalid profile " + profile);
}
-
- synchronized (mConnectedDevices) {
- if (state == BluetoothA2dp.STATE_CONNECTED) {
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
- continue;
- }
- // If A2DP device exists, this is either an active device change or
- // device config change
- String existingDevicekey = mConnectedDevices.keyAt(i);
- String deviceName = device.getName();
- String address = device.getAddress();
- String newDeviceKey = makeDeviceListKey(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
- int a2dpCodec = getA2dpCodec(device);
- // Device not equal to existing device, active device change
- if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
- mConnectedDevices.remove(existingDevicekey);
- mConnectedDevices.put(newDeviceKey, new DeviceListSpec(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
- address, a2dpCodec));
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_A2DP_ACTIVE_DEVICE_CHANGE,
- 0,
- 0,
- new BluetoothA2dpDeviceInfo(
- device, a2dpVolume, a2dpCodec),
- 0 /* delay */);
- return 0;
- } else {
- // Device config change for existing device
- handleBluetoothA2dpDeviceConfigChange(device);
- return 0;
- }
- }
- }
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ throw new IllegalArgumentException("Invalid state " + state);
}
-
- // New device connection or a device disconnect
- return setBluetoothA2dpDeviceConnectionStateInt(
- device, state, profile, suppressNoisyIntent,
- AudioSystem.DEVICE_NONE, a2dpVolume);
+ return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+ suppressNoisyIntent, a2dpVolume);
}
private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG =
@@ -4967,24 +3989,27 @@ public class AudioService extends IAudioService.Stub
AudioSystem.DEVICE_OUT_ALL_USB |
AudioSystem.DEVICE_OUT_HDMI;
+ /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) {
+ sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
+ newDevice, 0, null, 0);
+ }
+
private void onAccessoryPlugMediaUnmute(int newDevice) {
if (DEBUG_VOL) {
Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]",
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
- synchronized (mConnectedDevices) {
- if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
- && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
- && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
- && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
- && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
- {
- if (DEBUG_VOL) {
- Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
- newDevice, AudioSystem.getOutputDeviceName(newDevice)));
- }
- mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
+
+ if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+ && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
+ && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
+ && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
+ if (DEBUG_VOL) {
+ Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
+ newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
+ mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
}
}
@@ -4994,7 +4019,7 @@ public class AudioService extends IAudioService.Stub
// NOTE: Locking order for synchronized objects related to volume or ringer mode management:
// 1 mScoclient OR mSafeMediaVolumeState
- // 2 mSetModeDeathHandlers
+ // 2 mSetModeLock
// 3 mSettingsLock
// 4 VolumeStreamState.class
public class VolumeStreamState {
@@ -5138,11 +4163,11 @@ public class AudioService extends IAudioService.Stub
}
// must be called while synchronized VolumeStreamState.class
- public void applyDeviceVolume_syncVSS(int device) {
+ /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
int index;
if (mIsMuted) {
index = 0;
- } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
+ } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if ((device & mFullVolumeDevices) != 0) {
index = (mIndexMax + 5)/10;
@@ -5151,10 +4176,11 @@ public class AudioService extends IAudioService.Stub
} else {
index = (getIndex(device) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+ AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
public void applyAllVolumes() {
+ final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// apply device specific volumes first
int index;
@@ -5164,7 +4190,7 @@ public class AudioService extends IAudioService.Stub
if (mIsMuted) {
index = 0;
} else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- mAvrcpAbsVolSupported) {
+ isAvrcpAbsVolSupported) {
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
} else if ((device & mFullVolumeDevices) != 0) {
index = (mIndexMax + 5)/10;
@@ -5173,7 +4199,7 @@ public class AudioService extends IAudioService.Stub
} else {
index = (mIndexMap.valueAt(i) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+ AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
}
// apply default volume last: by convention , default device volume will be used
@@ -5183,7 +4209,7 @@ public class AudioService extends IAudioService.Stub
} else {
index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
}
- AudioSystem.setStreamVolumeIndex(
+ AudioSystem.setStreamVolumeIndexAS(
mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
}
}
@@ -5373,6 +4399,7 @@ public class AudioService extends IAudioService.Stub
}
public void checkFixedVolumeDevices() {
+ final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// ignore settings for fixed volume devices: volume should always be at max or 0
if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
@@ -5383,7 +4410,7 @@ public class AudioService extends IAudioService.Stub
|| (((device & mFixedVolumeDevices) != 0) && index != 0)) {
mIndexMap.put(device, mIndexMax);
}
- applyDeviceVolume_syncVSS(device);
+ applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
}
}
}
@@ -5465,11 +4492,13 @@ public class AudioService extends IAudioService.Stub
}
}
- private void setDeviceVolume(VolumeStreamState streamState, int device) {
+ /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
+
+ final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
synchronized (VolumeStreamState.class) {
// Apply volume
- streamState.applyDeviceVolume_syncVSS(device);
+ streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
// Apply change to all streams using this one as alias
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -5479,11 +4508,13 @@ public class AudioService extends IAudioService.Stub
// Make sure volume is also maxed out on A2DP device for aliased stream
// that may have a different device selected
int streamDevice = getDeviceForStream(streamType);
- if ((device != streamDevice) && mAvrcpAbsVolSupported &&
- ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
- mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+ if ((device != streamDevice) && isAvrcpAbsVolSupported
+ && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
+ isAvrcpAbsVolSupported);
}
- mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
+ isAvrcpAbsVolSupported);
}
}
}
@@ -5762,12 +4793,6 @@ public class AudioService extends IAudioService.Stub
}
}
- private void setForceUse(int usage, int config, String eventSource) {
- synchronized (mConnectedDevices) {
- setForceUseInt_SyncDevices(usage, config, eventSource);
- }
- }
-
private void onPersistSafeVolumeState(int state) {
Settings.Global.putInt(mContentResolver,
Settings.Global.AUDIO_SAFE_VOLUME_STATE,
@@ -5834,56 +4859,20 @@ public class AudioService extends IAudioService.Stub
onPlaySoundEffect(msg.arg1, msg.arg2);
break;
- case MSG_BTA2DP_DOCK_TIMEOUT:
- // msg.obj == address of BTA2DP device
- synchronized (mConnectedDevices) {
- makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
- }
- mAudioEventWakeLock.release();
- break;
-
case MSG_SET_FORCE_USE:
- case MSG_SET_FORCE_BT_A2DP_USE:
- setForceUse(msg.arg1, msg.arg2, (String) msg.obj);
- break;
-
- case MSG_BT_HEADSET_CNCT_FAILED:
- resetBluetoothSco();
- break;
-
- case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
- { WiredDeviceConnectionState connectState =
- (WiredDeviceConnectionState)msg.obj;
- mDeviceLogger.log(new WiredDevConnectEvent(connectState));
- onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
- connectState.mAddress, connectState.mName, connectState.mCaller);
- mAudioEventWakeLock.release();
+ {
+ final String eventSource = (String) msg.obj;
+ final int useCase = msg.arg1;
+ final int config = msg.arg2;
+ if (useCase == AudioSystem.FOR_MEDIA) {
+ Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from "
+ + eventSource);
+ break;
}
- break;
-
- case MSG_SET_A2DP_SRC_CONNECTION_STATE:
- onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_SET_A2DP_SINK_CONNECTION_STATE:
- onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_SET_HEARING_AID_CONNECTION_STATE:
- onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_A2DP_DEVICE_CONFIG_CHANGE:
- onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj);
- mAudioEventWakeLock.release();
- break;
-
- case MSG_A2DP_ACTIVE_DEVICE_CHANGE:
- onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj);
- mAudioEventWakeLock.release();
+ sForceUseLogger.log(
+ new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+ AudioSystem.setForceUse(useCase, config);
+ }
break;
case MSG_DISABLE_AUDIO_FOR_UID:
@@ -5892,35 +4881,10 @@ public class AudioService extends IAudioService.Stub
mAudioEventWakeLock.release();
break;
- case MSG_REPORT_NEW_ROUTES: {
- int N = mRoutesObservers.beginBroadcast();
- if (N > 0) {
- AudioRoutesInfo routes;
- synchronized (mCurAudioRoutes) {
- routes = new AudioRoutesInfo(mCurAudioRoutes);
- }
- while (N > 0) {
- N--;
- IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
- try {
- obs.dispatchAudioRoutesChanged(routes);
- } catch (RemoteException e) {
- }
- }
- }
- mRoutesObservers.finishBroadcast();
- observeDevicesForStreams(-1);
- break;
- }
-
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
- case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
- onSendBecomingNoisyIntent();
- break;
-
case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
@@ -5930,10 +4894,6 @@ public class AudioService extends IAudioService.Stub
onPersistSafeVolumeState(msg.arg1);
break;
- case MSG_BROADCAST_BT_CONNECTION_STATE:
- onBroadcastScoConnectionState(msg.arg1);
- break;
-
case MSG_SYSTEM_READY:
onSystemReady();
break;
@@ -6033,20 +4993,7 @@ public class AudioService extends IAudioService.Stub
if (mEncodedSurroundMode != newSurroundMode) {
// Send to AudioPolicyManager
sendEncodedSurroundMode(newSurroundMode, "SettingsObserver");
- synchronized(mConnectedDevices) {
- // Is HDMI connected?
- String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
- DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- if (deviceSpec != null) {
- // Toggle HDMI to retrigger broadcast with proper formats.
- setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
- "android"); // disconnect
- setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
- AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
- "android"); // reconnect
- }
- }
+ mDeviceBroker.toggleHdmiIfConnected_Async();
mEncodedSurroundMode = newSurroundMode;
mSurroundModeChanged = true;
} else {
@@ -6055,515 +5002,18 @@ public class AudioService extends IAudioService.Stub
}
}
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceAvailable(
- String address, String name, String eventSource, int a2dpCodec) {
- // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
- // audio policy manager
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- setBluetoothA2dpOnInt(true, eventSource);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
- // Reset A2DP suspend state each time a new sink is connected
- AudioSystem.setParameters("A2dpSuspended=false");
- mConnectedDevices.put(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
- new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, a2dpCodec));
- sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0);
- setCurrentAudioRouteNameIfPossible(name);
- }
-
- private void onSendBecomingNoisyIntent() {
- mDeviceLogger.log((new AudioEventLogger.StringEvent(
- "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
- sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
- if (address == null) {
- return;
- }
- synchronized (mA2dpAvrcpLock) {
- mAvrcpAbsVolSupported = false;
- }
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
- mConnectedDevices.remove(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
- // Remove A2DP routes as well
- setCurrentAudioRouteNameIfPossible(null);
- if (mDockAddress == address) {
- mDockAddress = null;
- }
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
- // prevent any activity on the A2DP audio output to avoid unwanted
- // reconnection of the sink.
- AudioSystem.setParameters("A2dpSuspended=true");
- // Retrieve deviceSpec before removing device
- String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
- DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
- int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat :
- AudioSystem.AUDIO_FORMAT_DEFAULT;
- // the device will be made unavailable later, so consider it disconnected right away
- mConnectedDevices.remove(deviceKey);
- // send the delayed message to make the device unavailable later
- queueMsgUnderWakeLock(mAudioHandler,
- MSG_BTA2DP_DOCK_TIMEOUT,
- a2dpCodec,
- 0,
- address,
- delayMs);
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpSrcAvailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.put(
- makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
- new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
- address, AudioSystem.AUDIO_FORMAT_DEFAULT));
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeA2dpSrcUnavailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.remove(
- makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
- }
-
- private void setHearingAidVolume(int index, int streamType) {
- synchronized (mHearingAidLock) {
- if (mHearingAid != null) {
- //hearing aid expect volume value in range -128dB to 0dB
- int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10,
- AudioSystem.DEVICE_OUT_HEARING_AID);
- if (gainDB < BT_HEARING_AID_GAIN_MIN)
- gainDB = BT_HEARING_AID_GAIN_MIN;
- mHearingAid.setVolume(gainDB);
- }
- }
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
- int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
- setHearingAidVolume(index, AudioSystem.STREAM_MUSIC);
-
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.put(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
- new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name,
- address, AudioSystem.AUDIO_FORMAT_DEFAULT));
- sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0);
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
- AudioSystem.DEVICE_OUT_HEARING_AID, 0,
- mStreamStates[AudioSystem.STREAM_MUSIC], 0);
- setCurrentAudioRouteNameIfPossible(name);
- }
-
- // must be called synchronized on mConnectedDevices
- private void makeHearingAidDeviceUnavailable(String address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- mConnectedDevices.remove(
- makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
- // Remove Hearing Aid routes as well
- setCurrentAudioRouteNameIfPossible(null);
- }
-
- // must be called synchronized on mConnectedDevices
- private void cancelA2dpDeviceTimeout() {
- mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
- }
-
- // must be called synchronized on mConnectedDevices
- private boolean hasScheduledA2dpDockTimeout() {
- return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
- }
-
- private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
- {
- if (btInfo == null) {
- return;
- }
-
- BluetoothDevice btDevice = btInfo.getBtDevice();
- int a2dpVolume = btInfo.getVolume();
- int a2dpCodec = btInfo.getCodec();
-
- if (btDevice == null) {
- return;
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state
- + " is dock: " + btDevice.isBluetoothDock());
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- btDevice.getAddress());
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- boolean isConnected = deviceSpec != null;
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // introduction of a delay for transient disconnections of docks when
- // power is rapidly turned off/on, this message will be canceled if
- // we reconnect the dock under a preset delay
- makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS);
- // the next time isConnected is evaluated, it will be false for the dock
- }
- } else {
- makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat);
- }
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- // this could be a reconnection after a transient disconnection
- cancelA2dpDeviceTimeout();
- mDockAddress = address;
- } else {
- // this could be a connection of another A2DP device before the timeout of
- // a dock: cancel the dock timeout, and make the dock unavailable now
- if (hasScheduledA2dpDockTimeout() && mDockAddress != null) {
- cancelA2dpDeviceTimeout();
- makeA2dpDeviceUnavailableNow(mDockAddress,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
- }
- if (a2dpVolume != -1) {
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- // Convert index to internal representation in VolumeStreamState
- a2dpVolume = a2dpVolume * 10;
- streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- "onSetA2dpSinkConnectionState");
- setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
- }
- makeA2dpDeviceAvailable(address, btDevice.getName(),
- "onSetA2dpSinkConnectionState", a2dpCodec);
- }
- }
- }
-
- private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
- {
- if (btInfo == null) {
- return;
- }
- BluetoothDevice btDevice = btInfo.getBtDevice();
-
- if (DEBUG_VOL) {
- Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state);
- }
- if (btDevice == null) {
- return;
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- boolean isConnected = deviceSpec != null;
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- makeA2dpSrcUnavailable(address);
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- makeA2dpSrcAvailable(address);
- }
- }
- }
-
- private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state)
- {
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state);
- }
- if (btDevice == null) {
- return;
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
-
- synchronized (mConnectedDevices) {
- final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
- btDevice.getAddress());
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- boolean isConnected = deviceSpec != null;
-
- if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
- makeHearingAidDeviceUnavailable(address);
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- makeHearingAidDeviceAvailable(address, btDevice.getName(),
- "onSetHearingAidConnectionState");
- }
- }
- }
-
- private void setCurrentAudioRouteNameIfPossible(String name) {
- synchronized (mCurAudioRoutes) {
- if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
- if (name != null || !isCurrentDeviceConnected()) {
- mCurAudioRoutes.bluetoothName = name;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- }
- }
-
- private boolean isCurrentDeviceConnected() {
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
- if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) {
- return true;
- }
- }
- return false;
- }
-
- private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo)
- {
- if (btInfo == null) {
- return;
- }
- BluetoothDevice btDevice = btInfo.getBtDevice();
- int a2dpCodec = btInfo.getCodec();
-
- if (btDevice == null) {
- return;
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "onBluetoothA2dpDeviceConfigChange addr=" + address));
-
- int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mConnectedDevices) {
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2dp config change ignored"));
- return;
- }
- final String key = makeDeviceListKey(device, address);
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- if (deviceSpec == null) {
- return;
- }
- // Device is connected
- if (deviceSpec.mDeviceCodecFormat != a2dpCodec) {
- deviceSpec.mDeviceCodecFormat = a2dpCodec;
- mConnectedDevices.replace(key, deviceSpec);
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
- + deviceSpec.mDeviceCodecFormat);
- }
- if (AudioSystem.handleDeviceConfigChange(device, address,
- btDevice.getName(), deviceSpec.mDeviceCodecFormat)
- != AudioSystem.AUDIO_STATUS_OK) {
- int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- // force A2DP device disconnection in case of error so that AudioService state is
- // consistent with audio policy manager state
- setBluetoothA2dpDeviceConnectionStateInt(
- btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
- false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
- }
- }
- }
-
- /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */
- public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) {
- if (btInfo == null) {
- return;
- }
- BluetoothDevice btDevice = btInfo.getBtDevice();
- int a2dpVolume = btInfo.getVolume();
- int a2dpCodec = btInfo.getCodec();
-
- if (btDevice == null) {
- return;
- }
- if (DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
- }
- String address = btDevice.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- address = "";
- }
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "onBluetoothA2dpActiveDeviceChange addr=" + address));
-
- int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mConnectedDevices) {
- if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
- mDeviceLogger.log(new AudioEventLogger.StringEvent(
- "A2dp config change ignored"));
- return;
- }
- final String key = makeDeviceListKey(device, address);
- final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
- if (deviceSpec == null) {
- return;
- }
-
- // Device is connected
- if (a2dpVolume != -1) {
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- // Convert index to internal representation in VolumeStreamState
- a2dpVolume = a2dpVolume * 10;
- streamState.setIndex(a2dpVolume, device,
- "onBluetoothA2dpActiveDeviceChange");
- setDeviceVolume(streamState, device);
- }
-
- if (AudioSystem.handleDeviceConfigChange(device, address,
- btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
- int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- // force A2DP device disconnection in case of error so that AudioService state is
- // consistent with audio policy manager state
- setBluetoothA2dpDeviceConnectionStateInt(
- btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
- false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
- }
- }
- }
-
public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
// address is not used for now, but may be used when multiple a2dp devices are supported
- synchronized (mA2dpAvrcpLock) {
- mAvrcpAbsVolSupported = support;
- sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+ mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
+ sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
mStreamStates[AudioSystem.STREAM_MUSIC], 0);
- }
- }
-
- private boolean handleDeviceConnection(boolean connect, int device, String address,
- String deviceName) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device)
- + " address:" + address + " name:" + deviceName + ")");
- }
- synchronized (mConnectedDevices) {
- String deviceKey = makeDeviceListKey(device, address);
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "deviceKey:" + deviceKey);
- }
- DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
- boolean isConnected = deviceSpec != null;
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected);
- }
- if (connect && !isConnected) {
- final int res = AudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- if (res != AudioSystem.AUDIO_STATUS_OK) {
- Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) +
- " due to command error " + res );
- return false;
- }
- mConnectedDevices.put(deviceKey, new DeviceListSpec(device,
- deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
- sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
- device, 0, null, 0);
- return true;
- } else if (!connect && isConnected) {
- AudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- // always remove even if disconnection failed
- mConnectedDevices.remove(deviceKey);
- return true;
- }
- Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec="
- + deviceSpec + ", connect=" + connect);
- }
- return false;
- }
-
- // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
- // sent if:
- // - none of these devices are connected anymore after one is disconnected AND
- // - the device being disconnected is actually used for music.
- // Access synchronized on mConnectedDevices
- int mBecomingNoisyIntentDevices =
- AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
- AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE |
- AudioSystem.DEVICE_OUT_HEARING_AID;
-
- // must be called before removing the device from mConnectedDevices
- // Called synchronized on mConnectedDevices
- // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
- // from AudioSystem
- private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
- int delay = 0;
- if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
- int devices = 0;
- for (int i = 0; i < mConnectedDevices.size(); i++) {
- int dev = mConnectedDevices.valueAt(i).mDeviceType;
- if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
- && ((dev & mBecomingNoisyIntentDevices) != 0)) {
- devices |= dev;
- }
- }
- if (musicDevice == AudioSystem.DEVICE_NONE) {
- musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- }
- // ignore condition on device being actually used for music when in communication
- // because music routing is altered in this case.
- // also checks whether media routing if affected by a dynamic policy
- if (((device == musicDevice) || isInCommunication()) && (device == devices)
- && !hasMediaDynamicPolicy()) {
- mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY);
- sendMsg(mAudioHandler,
- MSG_BROADCAST_AUDIO_BECOMING_NOISY,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
- delay = 1000;
- }
- }
-
- return delay;
}
/**
* @return true if there is currently a registered dynamic mixing policy that affects media
*/
- private boolean hasMediaDynamicPolicy() {
+ /*package*/ boolean hasMediaDynamicPolicy() {
synchronized (mAudioPolicies) {
if (mAudioPolicies.isEmpty()) {
return false;
@@ -6578,213 +5028,25 @@ public class AudioService extends IAudioService.Stub
}
}
- private void updateAudioRoutes(int device, int state)
- {
- int connType = 0;
-
- if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
- connType = AudioRoutesInfo.MAIN_HEADSET;
- } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
- device == AudioSystem.DEVICE_OUT_LINE) {
- connType = AudioRoutesInfo.MAIN_HEADPHONES;
- } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
- device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
- connType = AudioRoutesInfo.MAIN_HDMI;
- } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE||
- device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
- connType = AudioRoutesInfo.MAIN_USB;
- }
-
- synchronized (mCurAudioRoutes) {
- if (connType != 0) {
- int newConn = mCurAudioRoutes.mainType;
- if (state != 0) {
- newConn |= connType;
- } else {
- newConn &= ~connType;
- }
- if (newConn != mCurAudioRoutes.mainType) {
- mCurAudioRoutes.mainType = newConn;
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- }
- }
- }
-
- private void sendDeviceConnectionIntent(int device, int state, String address,
- String deviceName) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
- " state:0x" + Integer.toHexString(state) + " address:" + address +
- " name:" + deviceName + ");");
- }
- Intent intent = new Intent();
-
- if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 1);
- } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
- device == AudioSystem.DEVICE_OUT_LINE) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 0);
- } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone",
- AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
- == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
- } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) {
- if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
- == AudioSystem.DEVICE_STATE_AVAILABLE) {
- intent.setAction(Intent.ACTION_HEADSET_PLUG);
- intent.putExtra("microphone", 1);
- } else {
- // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
- return;
- }
- } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
- device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
- configureHdmiPlugIntent(intent, state);
- }
-
- if (intent.getAction() == null) {
- return;
- }
-
- intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
- intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
- intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
-
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
- AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_LINE |
- AudioSystem.DEVICE_OUT_ALL_USB;
-
- private void onSetWiredDeviceConnectionState(int device, int state, String address,
- String deviceName, String caller) {
- if (DEBUG_DEVICES) {
- Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
- + " state:" + Integer.toHexString(state)
- + " address:" + address
- + " deviceName:" + deviceName
- + " caller: " + caller + ");");
- }
-
- synchronized (mConnectedDevices) {
- if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
- setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
- }
-
- if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
- // change of connection state failed, bailout
- return;
- }
- if (state != 0) {
- if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
- setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
- }
- if ((device & mSafeMediaVolumeDevices) != 0) {
- sendMsg(mAudioHandler,
- MSG_CHECK_MUSIC_ACTIVE,
- SENDMSG_REPLACE,
- 0,
- 0,
- caller,
- MUSIC_ACTIVE_POLL_PERIOD_MS);
- }
- // Television devices without CEC service apply software volume on HDMI output
- if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
- mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
- checkAllFixedVolumeDevices();
- synchronized (mHdmiClientLock) {
- if (mHdmiManager != null && mHdmiPlaybackClient != null) {
- mHdmiCecSink = false;
- mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
- }
- }
- }
- if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
- sendEnabledSurroundFormats(mContentResolver, true);
- }
- } else {
- if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
- synchronized (mHdmiClientLock) {
- if (mHdmiManager != null) {
- mHdmiCecSink = false;
- }
- }
- }
- }
- sendDeviceConnectionIntent(device, state, address, deviceName);
- updateAudioRoutes(device, state);
- }
- }
-
- private void configureHdmiPlugIntent(Intent intent, int state) {
- intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
- intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
- if (state == 1) {
- ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
- int[] portGeneration = new int[1];
- int status = AudioSystem.listAudioPorts(ports, portGeneration);
- if (status == AudioManager.SUCCESS) {
- for (AudioPort port : ports) {
- if (port instanceof AudioDevicePort) {
- final AudioDevicePort devicePort = (AudioDevicePort) port;
- if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI ||
- devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) {
- // format the list of supported encodings
- int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
- if (formats.length > 0) {
- ArrayList<Integer> encodingList = new ArrayList(1);
- for (int format : formats) {
- // a format in the list can be 0, skip it
- if (format != AudioFormat.ENCODING_INVALID) {
- encodingList.add(format);
- }
- }
- int[] encodingArray = new int[encodingList.size()];
- for (int i = 0 ; i < encodingArray.length ; i++) {
- encodingArray[i] = encodingList.get(i);
- }
- intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
- }
- // find the maximum supported number of channels
- int maxChannels = 0;
- for (int mask : devicePort.channelMasks()) {
- int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
- if (channelCount > maxChannels) {
- maxChannels = channelCount;
- }
- }
- intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
- }
- }
- }
- }
+ /*package*/ void checkMusicActive(int deviceType, String caller) {
+ if ((deviceType & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
}
}
- /* cache of the address of the last dock the device was connected to */
- private String mDockAddress;
-
/**
* Receiver for misc intent broadcasts the Phone app cares about.
*/
private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final String action = intent.getAction();
int outDevice;
int inDevice;
int state;
@@ -6812,75 +5074,16 @@ public class AudioService extends IAudioService.Stub
}
// Low end docks have a menu to enable or disable audio
// (see mDockAudioMediaEnabled)
- if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
- ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) &&
- (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config,
- "ACTION_DOCK_EVENT intent"));
- AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
+ if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK)
+ || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED)
+ && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config,
+ "ACTION_DOCK_EVENT intent");
}
mDockState = dockState;
- } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
- BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- setBtScoActiveDevice(btDevice);
- } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
- boolean broadcast = false;
- int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
- synchronized (mScoClients) {
- int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- // broadcast intent if the connection was initated by AudioService
- if (!mScoClients.isEmpty() &&
- (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
- mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATING)) {
- broadcast = true;
- }
- switch (btState) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- setBluetoothScoOn(true);
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- setBluetoothScoOn(false);
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- // startBluetoothSco called after stopBluetoothSco
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
- && connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcast = false;
- break;
- }
- }
- // Tear down SCO if disconnected from external
- clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
- mScoAudioState = SCO_STATE_INACTIVE;
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- default:
- // do not broadcast CONNECTING or invalid state
- broadcast = false;
- break;
- }
- }
- if (broadcast) {
- broadcastScoConnectionState(scoAudioState);
- //FIXME: this is to maintain compatibility with deprecated intent
- // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
- Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
- newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
- sendStickyBroadcastToAll(newIntent);
- }
+ } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
+ || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ mDeviceBroker.receiveBtEvent(intent);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
if (mMonitorRotation) {
RotationHelper.enable();
@@ -6898,13 +5101,7 @@ public class AudioService extends IAudioService.Stub
if (mUserSwitchedReceived) {
// attempt to stop music playback for background user except on first user
// switch (i.e. first boot)
- sendMsg(mAudioHandler,
- MSG_BROADCAST_AUDIO_BECOMING_NOISY,
- SENDMSG_REPLACE,
- 0,
- 0,
- null,
- 0);
+ mDeviceBroker.broadcastBecomingNoisy();
}
mUserSwitchedReceived = true;
// the current audio focus owner is no longer valid
@@ -6938,7 +5135,7 @@ public class AudioService extends IAudioService.Stub
state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
if (state == BluetoothAdapter.STATE_OFF ||
state == BluetoothAdapter.STATE_TURNING_OFF) {
- disconnectAllBluetoothProfiles();
+ mDeviceBroker.disconnectAllBluetoothProfiles();
}
} else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
@@ -7178,22 +5375,17 @@ public class AudioService extends IAudioService.Stub
// take new state into account for streams muted by ringer mode
setRingerModeInt(getRingerModeInternal(), false);
}
-
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_SYSTEM,
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM,
cameraSoundForced ?
AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
- new String("handleConfigurationChanged"),
- 0);
-
+ "handleConfigurationChanged");
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
SENDMSG_QUEUE,
0,
0,
mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+
}
}
mVolumeController.setLayoutDirection(config.getLayoutDirection());
@@ -7202,28 +5394,6 @@ public class AudioService extends IAudioService.Stub
}
}
- // Handles request to override default use of A2DP for media.
- // Must be called synchronized on mConnectedDevices
- public void setBluetoothA2dpOnInt(boolean on, String eventSource) {
- synchronized (mBluetoothA2dpEnabledLock) {
- mBluetoothA2dpEnabled = on;
- mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
- setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
- mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
- eventSource);
- }
- }
-
- // Must be called synchronized on mConnectedDevices
- private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) {
- if (usage == AudioSystem.FOR_MEDIA) {
- sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
- SENDMSG_NOOP, 0, 0, null, 0);
- }
- mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource));
- AudioSystem.setForceUse(usage, config);
- }
-
@Override
public void setRingtonePlayer(IRingtonePlayer player) {
mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
@@ -7237,11 +5407,7 @@ public class AudioService extends IAudioService.Stub
@Override
public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
- synchronized (mCurAudioRoutes) {
- AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
- mRoutesObservers.register(observer);
- return routes;
- }
+ return mDeviceBroker.startWatchingRoutes(observer);
}
@@ -7283,9 +5449,9 @@ public class AudioService extends IAudioService.Stub
// the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
private int mSafeUsbMediaVolumeIndex;
// mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
- private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
- AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_USB_HEADSET;
+ /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET
+ | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+ | AudioSystem.DEVICE_OUT_USB_HEADSET;
// mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
// automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
@@ -7438,9 +5604,8 @@ public class AudioService extends IAudioService.Stub
mHdmiSystemAudioSupported = on;
final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
AudioSystem.FORCE_NONE;
- mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
- config, "setHdmiSystemAudioSupported"));
- AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config);
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config,
+ "setHdmiSystemAudioSupported");
}
device = getDevicesForStream(AudioSystem.STREAM_MUSIC);
}
@@ -7553,14 +5718,14 @@ public class AudioService extends IAudioService.Stub
// logs for wired + A2DP device connections:
// - wired: logged before onSetWiredDeviceConnectionState() is executed
// - A2DP: logged at reception of method call
- final private AudioEventLogger mDeviceLogger = new AudioEventLogger(
- LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection");
+ /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger(
+ LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI");
- final private AudioEventLogger mForceUseLogger = new AudioEventLogger(
+ static final AudioEventLogger sForceUseLogger = new AudioEventLogger(
LOG_NB_EVENTS_FORCE_USE,
"force use (logged before setForceUse() is executed)");
- final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
+ static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
"volume changes (logged when command received by AudioService)");
final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY,
@@ -7613,8 +5778,9 @@ public class AudioService extends IAudioService.Stub
dumpStreamStates(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
- pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType));
- pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName);
+ pw.print(" mMainType=0x"); pw.println(Integer.toHexString(
+ mDeviceBroker.getCurAudioRoutes().mainType));
+ pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName);
pw.println("\nOther state:");
pw.print(" mVolumeController="); pw.println(mVolumeController);
@@ -7630,7 +5796,8 @@ public class AudioService extends IAudioService.Stub
pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced);
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
- pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
+ pw.print(" mAvrcpAbsVolSupported=");
+ pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported());
dumpAudioPolicies(pw);
mDynPolicyLogger.dump(pw);
@@ -7643,11 +5810,11 @@ public class AudioService extends IAudioService.Stub
pw.println("\nEvent logs:");
mModeLogger.dump(pw);
pw.println("\n");
- mDeviceLogger.dump(pw);
+ sDeviceLogger.dump(pw);
pw.println("\n");
- mForceUseLogger.dump(pw);
+ sForceUseLogger.dump(pw);
pw.println("\n");
- mVolumeLogger.dump(pw);
+ sVolumeLogger.dump(pw);
}
private static String safeMediaVolumeStateToString(int state) {
@@ -8270,6 +6437,11 @@ public class AudioService extends IAudioService.Stub
}
//======================
+ // Audio device management
+ //======================
+ private final AudioDeviceBroker mDeviceBroker;
+
+ //======================
// Audio policy proxy
//======================
private static final class AudioDeviceArray {
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 9d9e35bdf2b2..7ccb45e97912 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -19,7 +19,7 @@ package com.android.server.audio;
import android.media.AudioManager;
import android.media.AudioSystem;
-import com.android.server.audio.AudioService.WiredDeviceConnectionState;
+import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState;
public class AudioServiceEvents {
@@ -89,9 +89,11 @@ public class AudioServiceEvents {
}
final static class VolumeEvent extends AudioEventLogger.Event {
- final static int VOL_ADJUST_SUGG_VOL = 0;
- final static int VOL_ADJUST_STREAM_VOL = 1;
- final static int VOL_SET_STREAM_VOL = 2;
+ static final int VOL_ADJUST_SUGG_VOL = 0;
+ static final int VOL_ADJUST_STREAM_VOL = 1;
+ static final int VOL_SET_STREAM_VOL = 2;
+ static final int VOL_SET_HEARING_AID_VOL = 3;
+ static final int VOL_SET_AVRCP_VOL = 4;
final int mOp;
final int mStream;
@@ -107,6 +109,24 @@ public class AudioServiceEvents {
mCaller = caller;
}
+ VolumeEvent(int op, int index, int gainDb) {
+ mOp = op;
+ mVal1 = index;
+ mVal2 = gainDb;
+ //unused
+ mStream = -1;
+ mCaller = null;
+ }
+
+ VolumeEvent(int op, int index) {
+ mOp = op;
+ mVal1 = index;
+ //unused
+ mVal2 = 0;
+ mStream = -1;
+ mCaller = null;
+ }
+
@Override
public String eventToString() {
switch (mOp) {
@@ -131,6 +151,15 @@ public class AudioServiceEvents {
.append(" flags:0x").append(Integer.toHexString(mVal2))
.append(") from ").append(mCaller)
.toString();
+ case VOL_SET_HEARING_AID_VOL:
+ return new StringBuilder("setHearingAidVolume:")
+ .append(" index:").append(mVal1)
+ .append(" gain dB:").append(mVal2)
+ .toString();
+ case VOL_SET_AVRCP_VOL:
+ return new StringBuilder("setAvrcpVolume:")
+ .append(" index:").append(mVal1)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
new file mode 100644
index 000000000000..bf325013c7da
--- /dev/null
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -0,0 +1,949 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * @hide
+ * Class to encapsulate all communication with Bluetooth services
+ */
+public class BtHelper {
+
+ private static final String TAG = "AS.BtHelper";
+
+ private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+ BtHelper(@NonNull AudioDeviceBroker broker) {
+ mDeviceBroker = broker;
+ }
+
+ // List of clients having issued a SCO start request
+ private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
+
+ // BluetoothHeadset API to control SCO connection
+ private BluetoothHeadset mBluetoothHeadset;
+
+ // Bluetooth headset device
+ private BluetoothDevice mBluetoothHeadsetDevice;
+
+ // Indicate if SCO audio connection is currently active and if the initiator is
+ // audio service (internal) or bluetooth headset (external)
+ private int mScoAudioState;
+ // SCO audio state is not active
+ private static final int SCO_STATE_INACTIVE = 0;
+ // SCO audio activation request waiting for headset service to connect
+ private static final int SCO_STATE_ACTIVATE_REQ = 1;
+ // SCO audio state is active or starting due to a request from AudioManager API
+ private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
+ // SCO audio deactivation request waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_REQ = 4;
+ // SCO audio deactivation in progress, waiting for Bluetooth audio intent
+ private static final int SCO_STATE_DEACTIVATING = 5;
+
+ // SCO audio state is active due to an action in BT handsfree (either voice recognition or
+ // in call audio)
+ private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+
+ // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+ // originated from an app targeting an API version before JB MR2 and raw audio after that.
+ private int mScoAudioMode;
+ // SCO audio mode is undefined
+ /*package*/ static final int SCO_MODE_UNDEFINED = -1;
+ // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
+ /*package*/ static final int SCO_MODE_VIRTUAL_CALL = 0;
+ // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
+ private static final int SCO_MODE_RAW = 1;
+ // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
+ private static final int SCO_MODE_VR = 2;
+
+ private static final int SCO_MODE_MAX = 2;
+
+ // Current connection state indicated by bluetooth headset
+ private int mScoConnectionState;
+
+ private static final int BT_HEARING_AID_GAIN_MIN = -128;
+
+ @GuardedBy("mDeviceBroker.mHearingAidLock")
+ private BluetoothHearingAid mHearingAid;
+
+ // Reference to BluetoothA2dp to query for AbsoluteVolume.
+ @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+ private BluetoothA2dp mA2dp;
+ // If absolute volume is supported in AVRCP device
+ @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+ private boolean mAvrcpAbsVolSupported = false;
+
+ //----------------------------------------------------------------------
+ /*package*/ static class BluetoothA2dpDeviceInfo {
+ private final @NonNull BluetoothDevice mBtDevice;
+ private final int mVolume;
+ private final int mCodec;
+
+ BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
+ this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+
+ BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
+ mBtDevice = btDevice;
+ mVolume = volume;
+ mCodec = codec;
+ }
+
+ public @NonNull BluetoothDevice getBtDevice() {
+ return mBtDevice;
+ }
+
+ public int getVolume() {
+ return mVolume;
+ }
+
+ public int getCodec() {
+ return mCodec;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Interface for AudioDeviceBroker
+
+ /*package*/ void onSystemReady() {
+ mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
+ resetBluetoothSco();
+ getBluetoothHeadset();
+
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ sendStickyBroadcastToAll(newIntent);
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
+ adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
+ }
+ }
+
+ @GuardedBy("mBluetoothA2dpEnabledLock")
+ /*package*/ void onAudioServerDiedRestoreA2dp() {
+ final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
+ ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
+ }
+
+ @GuardedBy("mA2dpAvrcpLock")
+ /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+ return (mA2dp != null && mAvrcpAbsVolSupported);
+ }
+
+ @GuardedBy("mA2dpAvrcpLock")
+ /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+ mAvrcpAbsVolSupported = supported;
+ }
+
+ /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) {
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ if (mA2dp == null) {
+ if (AudioService.DEBUG_VOL) {
+ Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp");
+ return;
+ }
+ }
+ if (!mAvrcpAbsVolSupported) {
+ return;
+ }
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
+ }
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10));
+ mA2dp.setAvrcpAbsoluteVolume(index / 10);
+ }
+ }
+
+ @GuardedBy("mA2dpAvrcpLock")
+ /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+ if (mA2dp == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ if (btCodecStatus == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+ if (btCodecConfig == null) {
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+ }
+
+ /*package*/ void receiveBtEvent(Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
+ BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ setBtScoActiveDevice(btDevice);
+ } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ boolean broadcast = false;
+ int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ synchronized (mScoClients) {
+ int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ // broadcast intent if the connection was initated by AudioService
+ if (!mScoClients.isEmpty()
+ && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
+ || mScoAudioState == SCO_STATE_ACTIVATE_REQ
+ || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
+ || mScoAudioState == SCO_STATE_DEACTIVATING)) {
+ broadcast = true;
+ }
+ switch (btState) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ // startBluetoothSco called after stopBluetoothSco
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+ && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcast = false;
+ break;
+ }
+ }
+ // Tear down SCO if disconnected from external
+ clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ break;
+ default:
+ // do not broadcast CONNECTING or invalid state
+ broadcast = false;
+ break;
+ }
+ }
+ if (broadcast) {
+ broadcastScoConnectionState(scoAudioState);
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
+ sendStickyBroadcastToAll(newIntent);
+ }
+ }
+ }
+
+ /**
+ *
+ * @return false if SCO isn't connected
+ */
+ /*package*/ boolean isBluetoothScoOn() {
+ synchronized (mScoClients) {
+ if ((mBluetoothHeadset != null)
+ && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+ Log.w(TAG, "isBluetoothScoOn(true) returning false because "
+ + mBluetoothHeadsetDevice + " is not in audio connected mode");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Disconnect all SCO connections started by {@link AudioManager} except those started by
+ * {@param exceptPid}
+ *
+ * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
+ */
+ /*package*/ void disconnectBluetoothSco(int exceptPid) {
+ synchronized (mScoClients) {
+ checkScoAudioState();
+ if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
+ return;
+ }
+ clearAllScoClients(exceptPid, true);
+ }
+ }
+
+ /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
+ @NonNull String eventSource) {
+ ScoClient client = getScoClient(cb, true);
+ // The calling identity must be cleared before calling ScoClient.incCount().
+ // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ // The caller identity must be cleared after getScoClient() because it is needed if a new
+ // client is created.
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ eventSource += " client count before=" + client.getCount();
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+ client.incCount(scoAudioMode);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Null ScoClient", e);
+ }
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) {
+ ScoClient client = getScoClient(cb, false);
+ // The calling identity must be cleared before calling ScoClient.decCount().
+ // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ final long ident = Binder.clearCallingIdentity();
+ if (client != null) {
+ eventSource += " client count before=" + client.getCount();
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+ client.decCount();
+ }
+ Binder.restoreCallingIdentity(ident);
+ }
+
+
+ /*package*/ void setHearingAidVolume(int index, int streamType) {
+ synchronized (mDeviceBroker.mHearingAidLock) {
+ if (mHearingAid == null) {
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setHearingAidVolume: null mHearingAid");
+ }
+ return;
+ }
+ //hearing aid expect volume value in range -128dB to 0dB
+ int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
+ AudioSystem.DEVICE_OUT_HEARING_AID);
+ if (gainDB < BT_HEARING_AID_GAIN_MIN) {
+ gainDB = BT_HEARING_AID_GAIN_MIN;
+ }
+ if (AudioService.DEBUG_VOL) {
+ Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+ + index + " gain=" + gainDB);
+ }
+ AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+ mHearingAid.setVolume(gainDB);
+ }
+ }
+
+ //----------------------------------------------------------------------
+ private void broadcastScoConnectionState(int state) {
+ mDeviceBroker.broadcastScoConnectionState(state);
+ }
+
+ /*package*/ void onBroadcastScoConnectionState(int state) {
+ if (state == mScoConnectionState) {
+ return;
+ }
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+ mScoConnectionState);
+ sendStickyBroadcastToAll(newIntent);
+ mScoConnectionState = state;
+ }
+
+ private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
+ if (btDevice == null) {
+ return true;
+ }
+ String address = btDevice.getAddress();
+ BluetoothClass btClass = btDevice.getBluetoothClass();
+ int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+ int[] outDeviceTypes = {
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
+ };
+ if (btClass != null) {
+ switch (btClass.getDeviceClass()) {
+ case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+ outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
+ break;
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
+ break;
+ }
+ }
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+ String btDeviceName = btDevice.getName();
+ boolean result = false;
+ if (isActive) {
+ result |= mDeviceBroker.handleDeviceConnection(
+ isActive, outDeviceTypes[0], address, btDeviceName);
+ } else {
+ for (int outDeviceType : outDeviceTypes) {
+ result |= mDeviceBroker.handleDeviceConnection(
+ isActive, outDeviceType, address, btDeviceName);
+ }
+ }
+ // handleDeviceConnection() && result to make sure the method get executed
+ result = mDeviceBroker.handleDeviceConnection(
+ isActive, inDevice, address, btDeviceName) && result;
+ return result;
+ }
+
+ private void setBtScoActiveDevice(BluetoothDevice btDevice) {
+ synchronized (mScoClients) {
+ Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
+ final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+ if (Objects.equals(btDevice, previousActiveDevice)) {
+ return;
+ }
+ if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+ Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+ + previousActiveDevice);
+ }
+ if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+ Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+ // set mBluetoothHeadsetDevice to null when failing to add new device
+ btDevice = null;
+ }
+ mBluetoothHeadsetDevice = btDevice;
+ if (mBluetoothHeadsetDevice == null) {
+ resetBluetoothSco();
+ }
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ final BluetoothDevice btDevice;
+ List<BluetoothDevice> deviceList;
+ switch(profile) {
+ case BluetoothProfile.A2DP:
+ synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+ mA2dp = (BluetoothA2dp) proxy;
+ deviceList = mA2dp.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ if (btDevice == null) {
+ Log.e(TAG, "Invalid null device in BT profile listener");
+ return;
+ }
+ final @BluetoothProfile.BtProfileState int state =
+ mA2dp.getConnectionState(btDevice);
+ mDeviceBroker.handleSetA2dpSinkConnectionState(
+ state, new BluetoothA2dpDeviceInfo(btDevice));
+ }
+ }
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ deviceList = proxy.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ final @BluetoothProfile.BtProfileState int state =
+ proxy.getConnectionState(btDevice);
+ mDeviceBroker.handleSetA2dpSourceConnectionState(
+ state, new BluetoothA2dpDeviceInfo(btDevice));
+ }
+ break;
+
+ case BluetoothProfile.HEADSET:
+ synchronized (mScoClients) {
+ // Discard timeout message
+ mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
+ // Refresh SCO audio state
+ checkScoAudioState();
+ // Continue pending action if any
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
+ || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
+ boolean status = false;
+ if (mBluetoothHeadsetDevice != null) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVATE_REQ:
+ status = connectBluetoothScoAudioHelper(
+ mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode);
+ if (status) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ }
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ status = disconnectBluetoothScoAudioHelper(
+ mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode);
+ if (status) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ }
+ break;
+ }
+ }
+ if (!status) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+ }
+ break;
+
+ case BluetoothProfile.HEARING_AID:
+ mHearingAid = (BluetoothHearingAid) proxy;
+ deviceList = mHearingAid.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ final @BluetoothProfile.BtProfileState int state =
+ mHearingAid.getConnectionState(btDevice);
+ mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+ btDevice, state,
+ /*suppressNoisyIntent*/ false,
+ /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
+ /*eventSource*/ "mBluetoothProfileServiceListener");
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ public void onServiceDisconnected(int profile) {
+
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mDeviceBroker.handleDisconnectA2dp();
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ mDeviceBroker.handleDisconnectA2dpSink();
+ break;
+
+ case BluetoothProfile.HEADSET:
+ disconnectHeadset();
+ break;
+
+ case BluetoothProfile.HEARING_AID:
+ mDeviceBroker.handleDisconnectHearingAid();
+ break;
+
+ default:
+ break;
+ }
+ }
+ };
+
+ void disconnectAllBluetoothProfiles() {
+ mDeviceBroker.handleDisconnectA2dp();
+ mDeviceBroker.handleDisconnectA2dpSink();
+ disconnectHeadset();
+ mDeviceBroker.handleDisconnectHearingAid();
+ }
+
+ private void disconnectHeadset() {
+ synchronized (mScoClients) {
+ setBtScoActiveDevice(null);
+ mBluetoothHeadset = null;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ private class ScoClient implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+ private int mCreatorPid;
+ private int mStartcount; // number of SCO connections started by this client
+
+ ScoClient(IBinder cb) {
+ mCb = cb;
+ mCreatorPid = Binder.getCallingPid();
+ mStartcount = 0;
+ }
+
+ public void binderDied() {
+ synchronized (mScoClients) {
+ Log.w(TAG, "SCO client died");
+ int index = mScoClients.indexOf(this);
+ if (index < 0) {
+ Log.w(TAG, "unregistered SCO client died");
+ } else {
+ clearCount(true);
+ mScoClients.remove(this);
+ }
+ }
+ }
+
+ public void incCount(int scoAudioMode) {
+ synchronized (mScoClients) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
+ if (mStartcount == 0) {
+ try {
+ mCb.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // client has already died!
+ Log.w(TAG, "ScoClient incCount() could not link to "
+ + mCb + " binder death");
+ }
+ }
+ mStartcount++;
+ }
+ }
+
+ public void decCount() {
+ synchronized (mScoClients) {
+ if (mStartcount == 0) {
+ Log.w(TAG, "ScoClient.decCount() already 0");
+ } else {
+ mStartcount--;
+ if (mStartcount == 0) {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "decCount() going to 0 but not registered to binder");
+ }
+ }
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+ }
+ }
+ }
+
+ public void clearCount(boolean stopSco) {
+ synchronized (mScoClients) {
+ if (mStartcount != 0) {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "clearCount() mStartcount: "
+ + mStartcount + " != 0 but not registered to binder");
+ }
+ }
+ mStartcount = 0;
+ if (stopSco) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+ }
+ }
+ }
+
+ public int getCount() {
+ return mStartcount;
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+
+ public int getPid() {
+ return mCreatorPid;
+ }
+
+ public int totalCount() {
+ synchronized (mScoClients) {
+ int count = 0;
+ for (ScoClient mScoClient : mScoClients) {
+ count += mScoClient.getCount();
+ }
+ return count;
+ }
+ }
+
+ private void requestScoState(int state, int scoAudioMode) {
+ checkScoAudioState();
+ int clientCount = totalCount();
+ if (clientCount != 0) {
+ Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+ + ", clientCount=" + clientCount);
+ return;
+ }
+ if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ // Make sure that the state transitions to CONNECTING even if we cannot initiate
+ // the connection.
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+ // currently controlled by the same client process.
+ synchronized (mDeviceBroker.mSetModeLock) {
+ int modeOwnerPid = mDeviceBroker.getSetModeDeathHandlers().isEmpty()
+ ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid();
+ if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
+ Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+ + modeOwnerPid + " != creatorPid " + mCreatorPid);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ return;
+ }
+ switch (mScoAudioState) {
+ case SCO_STATE_INACTIVE:
+ mScoAudioMode = scoAudioMode;
+ if (scoAudioMode == SCO_MODE_UNDEFINED) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ if (mBluetoothHeadsetDevice != null) {
+ mScoAudioMode = Settings.Global.getInt(
+ mDeviceBroker.getContentResolver(),
+ "bluetooth_sco_channel_"
+ + mBluetoothHeadsetDevice.getAddress(),
+ SCO_MODE_VIRTUAL_CALL);
+ if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ }
+ }
+ }
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ } else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " connection, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ }
+ if (mBluetoothHeadsetDevice == null) {
+ Log.w(TAG, "requestScoState: no active device while connecting,"
+ + " mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+ + " failed, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_DEACTIVATING:
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to connect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+
+ }
+ }
+ } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVE_INTERNAL:
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ } else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " disconnection, mScoAudioMode=" + mScoAudioMode);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ }
+ if (mBluetoothHeadsetDevice == null) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_ACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to disconnect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------
+ // Utilities
+ private void sendStickyBroadcastToAll(Intent intent) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+ BluetoothDevice device, int scoAudioMode) {
+ switch (scoAudioMode) {
+ case SCO_MODE_RAW:
+ return bluetoothHeadset.disconnectAudio();
+ case SCO_MODE_VIRTUAL_CALL:
+ return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
+ case SCO_MODE_VR:
+ return bluetoothHeadset.stopVoiceRecognition(device);
+ default:
+ return false;
+ }
+ }
+
+ private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+ BluetoothDevice device, int scoAudioMode) {
+ switch (scoAudioMode) {
+ case SCO_MODE_RAW:
+ return bluetoothHeadset.connectAudio();
+ case SCO_MODE_VIRTUAL_CALL:
+ return bluetoothHeadset.startScoUsingVirtualVoiceCall();
+ case SCO_MODE_VR:
+ return bluetoothHeadset.startVoiceRecognition(device);
+ default:
+ return false;
+ }
+ }
+
+ /*package*/ void resetBluetoothSco() {
+ synchronized (mScoClients) {
+ clearAllScoClients(0, false);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ AudioSystem.setParameters("A2dpSuspended=false");
+ mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
+ }
+
+
+ private void checkScoAudioState() {
+ synchronized (mScoClients) {
+ if (mBluetoothHeadset != null
+ && mBluetoothHeadsetDevice != null
+ && mScoAudioState == SCO_STATE_INACTIVE
+ && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ }
+ }
+
+
+ private ScoClient getScoClient(IBinder cb, boolean create) {
+ synchronized (mScoClients) {
+ for (ScoClient existingClient : mScoClients) {
+ if (existingClient.getBinder() == cb) {
+ return existingClient;
+ }
+ }
+ if (create) {
+ ScoClient newClient = new ScoClient(cb);
+ mScoClients.add(newClient);
+ return newClient;
+ }
+ return null;
+ }
+ }
+
+ private void clearAllScoClients(int exceptPid, boolean stopSco) {
+ synchronized (mScoClients) {
+ ScoClient savedClient = null;
+ for (ScoClient cl : mScoClients) {
+ if (cl.getPid() != exceptPid) {
+ cl.clearCount(stopSco);
+ } else {
+ savedClient = cl;
+ }
+ }
+ mScoClients.clear();
+ if (savedClient != null) {
+ mScoClients.add(savedClient);
+ }
+ }
+ }
+
+ private boolean getBluetoothHeadset() {
+ boolean result = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ result = adapter.getProfileProxy(mDeviceBroker.getContext(),
+ mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
+ }
+ // If we could not get a bluetooth headset proxy, send a failure message
+ // without delay to reset the SCO audio state and clear SCO clients.
+ // If we could get a proxy, send a delayed failure message that will reset our state
+ // in case we don't receive onServiceConnected().
+ mDeviceBroker.handleFailureToConnectToBtHeadsetService(
+ result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+ return result;
+ }
+
+ private int mapBluetoothCodecToAudioFormat(int btCodecType) {
+ switch (btCodecType) {
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+ return AudioSystem.AUDIO_FORMAT_SBC;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+ return AudioSystem.AUDIO_FORMAT_AAC;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+ return AudioSystem.AUDIO_FORMAT_APTX;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+ return AudioSystem.AUDIO_FORMAT_APTX_HD;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+ return AudioSystem.AUDIO_FORMAT_LDAC;
+ default:
+ return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index dc564ba69e0d..3a25d980e97a 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -202,7 +202,7 @@ public final class PlaybackActivityMonitor
if (mPrivilegedAlarmActiveCount++ == 0) {
mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
- AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
}
} else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
@@ -211,7 +211,7 @@ public final class PlaybackActivityMonitor
if (AudioSystem.getStreamVolumeIndex(
AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
mMaxAlarmVolume) {
- AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 41fedc5fab45..15d66e6e646f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -29,10 +29,12 @@ import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
+import android.app.KeyguardManager;
import android.app.TaskStackListener;
import android.app.UserSwitchObserver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -377,6 +379,13 @@ public class BiometricService extends SystemService {
new BiometricTaskStackListener();
private final Random mRandom = new Random();
+ // TODO(b/123378871): Remove when moved.
+ // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app)
+ // receiver. BiometricService internally launches CDCA which invokes BiometricService to
+ // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl
+ // method to poke BiometricService - the result will then be forwarded to this receiver.
+ private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
+
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
private AuthSession mCurrentAuthSession;
@@ -705,6 +714,22 @@ public class BiometricService extends SystemService {
}
}
+ // Launch CDC instead if necessary. CDC will return results through an AIDL call, since
+ // we can't get activity results. Store the receiver somewhere so we can forward the
+ // result back to the client.
+ // TODO(b/123378871): Remove when moved.
+ if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) {
+ mConfirmDeviceCredentialReceiver = receiver;
+ final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
+ // Use this so we don't need to duplicate logic..
+ final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
+ null /* description */);
+ // Then give it the bundle to do magic behavior..
+ intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
+ getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+ return;
+ }
+
mHandler.post(() -> {
final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
final int modality = result.first;
@@ -745,6 +770,36 @@ public class BiometricService extends SystemService {
});
}
+ @Override // Binder call
+ public void onConfirmDeviceCredentialSuccess() {
+ checkInternalPermission();
+ if (mConfirmDeviceCredentialReceiver == null) {
+ Slog.w(TAG, "onCDCASuccess null!");
+ return;
+ }
+ try {
+ mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ mConfirmDeviceCredentialReceiver = null;
+ }
+
+ @Override // Binder call
+ public void onConfirmDeviceCredentialError(int error, String message) {
+ checkInternalPermission();
+ if (mConfirmDeviceCredentialReceiver == null) {
+ Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+ return;
+ }
+ try {
+ mConfirmDeviceCredentialReceiver.onError(error, message);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ mConfirmDeviceCredentialReceiver = null;
+ }
+
/**
* authenticate() (above) which is called from BiometricPrompt determines which
* modality/modalities to start authenticating with. authenticateInternal() should only be
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 3db6a74a1c6c..fcf821f23ae9 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -48,14 +48,12 @@ import android.os.SELinux;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import android.util.StatsLog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemServerInitThreadPool;
-import com.android.server.biometrics.AuthenticationClient;
import com.android.server.biometrics.BiometricServiceBase;
import com.android.server.biometrics.BiometricUtils;
import com.android.server.biometrics.ClientMonitor;
@@ -588,11 +586,6 @@ public class FingerprintService extends BiometricServiceBase {
public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
mHandler.post(() -> {
FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
- if (getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
- && getCurrentClient() instanceof AuthenticationClient) {
- // Ignore enrollment acquisitions or acquisitions when we are locked out.
- StatsLog.write(StatsLog.FINGERPRINT_ACQUIRED, mCurrentUserId, mIsCrypto);
- }
});
}
@@ -602,22 +595,6 @@ public class FingerprintService extends BiometricServiceBase {
mHandler.post(() -> {
Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
FingerprintService.super.handleAuthenticated(fp, token);
- // Send authentication to statsd.
- final boolean authenticated = fingerId != 0;
- StatsLog.write(StatsLog.FINGERPRINT_AUTHENTICATED, mCurrentUserId, mIsCrypto,
- authenticated);
- if (!authenticated) {
- // If we failed to authenticate because of a lockout, inform statsd.
- final int lockoutMode = getLockoutMode();
- if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
- StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
- mIsCrypto, StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__LOCKOUT);
- } else if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
- StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
- mIsCrypto,
- StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__PERMANENT_LOCKOUT);
- }
- }
});
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 9ea73fbb1882..d0cff25dbf05 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -152,6 +152,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
// Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected;
+ // Indicates the user was notified of a successful captive portal login since a portal was
+ // last detected.
+ public boolean captivePortalLoginNotified;
+
// Networks are lingered when they become unneeded as a result of their NetworkRequests being
// satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// network is taken down. This usually only happens to the default network. Lingering ends with
@@ -618,18 +622,19 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
}
public String toString() {
- return "NetworkAgentInfo{ ni{" + networkInfo + "} " +
- "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " +
- "lp{" + linkProperties + "} " +
- "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " +
- "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
- "created{" + created + "} lingering{" + isLingering() + "} " +
- "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
- "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
- "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
- "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
- "clat{" + clatd + "} " +
- "}";
+ return "NetworkAgentInfo{ ni{" + networkInfo + "} "
+ + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} "
+ + "lp{" + linkProperties + "} "
+ + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ + "created{" + created + "} lingering{" + isLingering() + "} "
+ + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+ + "clat{" + clatd + "} "
+ + "}";
}
public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 36a2476d2ceb..b50477bc120f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -16,13 +16,16 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -31,15 +34,12 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.widget.Toast;
+
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
public class NetworkNotificationManager {
@@ -47,7 +47,8 @@ public class NetworkNotificationManager {
LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
- SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+ SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+ LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN);
public final int eventId;
@@ -192,6 +193,9 @@ public class NetworkNotificationManager {
details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
}
+ } else if (notifyType == NotificationType.LOGGED_IN) {
+ title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
+ details = r.getString(R.string.captive_portal_logged_in_detailed);
} else if (notifyType == NotificationType.NETWORK_SWITCH) {
String fromTransport = getTransportName(transportType);
String toTransport = getTransportName(getFirstTransportType(switchToNai));
@@ -239,6 +243,18 @@ public class NetworkNotificationManager {
}
}
+ /**
+ * Clear the notification with the given id, only if it matches the given type.
+ */
+ public void clearNotification(int id, NotificationType notifyType) {
+ final int previousEventId = mNotificationTypeMap.get(id);
+ final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+ if (notifyType != previousNotifyType) {
+ return;
+ }
+ clearNotification(id);
+ }
+
public void clearNotification(int id) {
if (mNotificationTypeMap.indexOfKey(id) < 0) {
return;
@@ -290,6 +306,10 @@ public class NetworkNotificationManager {
return (t != null) ? t.name() : "UNKNOWN";
}
+ /**
+ * A notification with a higher number will take priority over a notification with a lower
+ * number.
+ */
private static int priority(NotificationType t) {
if (t == null) {
return 0;
@@ -302,6 +322,7 @@ public class NetworkNotificationManager {
case NETWORK_SWITCH:
return 2;
case LOST_INTERNET:
+ case LOGGED_IN:
return 1;
default:
return 0;
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index fdddccd20714..a671287324af 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -309,22 +309,4 @@ public class ProxyTracker {
}
}
}
-
- /**
- * Enable or disable the default proxy.
- *
- * This sets the flag for enabling/disabling the default proxy and sends the broadcast
- * if applicable.
- * @param enabled whether the default proxy should be enabled.
- */
- public void setDefaultProxyEnabled(final boolean enabled) {
- synchronized (mProxyLock) {
- if (mDefaultProxyEnabled != enabled) {
- mDefaultProxyEnabled = enabled;
- if (mGlobalProxy == null && mDefaultProxy != null) {
- sendProxyBroadcast();
- }
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c72c9ddf3f7a..62a1b036daa0 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -793,6 +793,8 @@ public class Vpn {
}
}
+ lp.setHttpProxy(mConfig.proxyInfo);
+
if (!allowIPv4) {
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 5ed626342d31..e268e4458986 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -48,7 +48,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.FactoryTest;
import android.os.IBinder;
-import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -255,21 +254,6 @@ public final class ContentService extends IContentService.Stub {
}
}
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- try {
- return super.onTransact(code, data, reply, flags);
- } catch (RuntimeException e) {
- // The content service only throws security exceptions, so let's
- // log all others.
- if (!(e instanceof SecurityException)) {
- Slog.wtf(TAG, "Content Service Crash", e);
- }
- throw e;
- }
- }
-
/*package*/ ContentService(Context context, boolean factoryTest) {
mContext = context;
mFactoryTest = factoryTest;
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 3a58160cae8d..eb0ed0a62ebf 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -16,6 +16,13 @@
package com.android.server.display;
+import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME;
+import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED;
+import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
+import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_SATURATION;
@@ -40,13 +47,17 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.ColorSpace;
import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.ColorDisplayManager.AutoMode;
+import android.hardware.display.ColorDisplayManager.ColorMode;
import android.hardware.display.IColorDisplayManager;
+import android.hardware.display.Time;
import android.net.Uri;
import android.opengl.Matrix;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.provider.Settings.System;
@@ -55,7 +66,6 @@ import android.util.Slog;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
-
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
@@ -65,7 +75,6 @@ import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -103,59 +112,17 @@ public final class ColorDisplayService extends SystemService {
private static final int MSG_APPLY_GLOBAL_SATURATION = 2;
/**
+ * Return value if a setting has not been set.
+ */
+ private static final int NOT_SET = -1;
+
+ /**
* Evaluator used to animate color matrix transitions.
*/
private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
- private final TintController mNightDisplayTintController = new TintController() {
-
- private float[] mMatrixNightDisplay = new float[16];
- private final float[] mColorTempCoefficients = new float[9];
-
- /**
- * Set coefficients based on whether the color matrix is linear or not.
- */
- @Override
- public void setUp(Context context, boolean needsLinear) {
- final String[] coefficients = context.getResources().getStringArray(needsLinear
- ? R.array.config_nightDisplayColorTemperatureCoefficients
- : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
- for (int i = 0; i < 9 && i < coefficients.length; i++) {
- mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
- }
- }
-
- @Override
- public void setMatrix(int cct) {
- if (mMatrixNightDisplay.length != 16) {
- Slog.d(TAG, "The display transformation matrix must be 4x4");
- return;
- }
-
- Matrix.setIdentityM(mMatrixNightDisplay, 0);
-
- final float squareTemperature = cct * cct;
- final float red = squareTemperature * mColorTempCoefficients[0]
- + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
- final float green = squareTemperature * mColorTempCoefficients[3]
- + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
- final float blue = squareTemperature * mColorTempCoefficients[6]
- + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
- mMatrixNightDisplay[0] = red;
- mMatrixNightDisplay[5] = green;
- mMatrixNightDisplay[10] = blue;
- }
-
- @Override
- public float[] getMatrix() {
- return isActivated() ? mMatrixNightDisplay : MATRIX_IDENTITY;
- }
-
- @Override
- public int getLevel() {
- return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
- }
- };
+ private final NightDisplayTintController mNightDisplayTintController =
+ new NightDisplayTintController();
private final TintController mDisplayWhiteBalanceTintController = new TintController() {
// Three chromaticity coordinates per color: X, Y, and Z
@@ -198,7 +165,7 @@ public final class ColorDisplayService extends SystemService {
}
float[] displayRedGreenBlueXYZ =
- new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
+ new float[NUM_DISPLAY_PRIMARIES_VALS - NUM_VALUES_PER_PRIMARY];
float[] displayWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
for (int i = 0; i < displayRedGreenBlueXYZ.length; i++) {
displayRedGreenBlueXYZ[i] = Float.parseFloat(displayPrimariesValues[i]);
@@ -209,10 +176,10 @@ public final class ColorDisplayService extends SystemService {
}
final ColorSpace.Rgb displayColorSpaceRGB = new ColorSpace.Rgb(
- "Display Color Space",
- displayRedGreenBlueXYZ,
- displayWhiteXYZ,
- 2.2f // gamma, unused for display white balance
+ "Display Color Space",
+ displayRedGreenBlueXYZ,
+ displayWhiteXYZ,
+ 2.2f // gamma, unused for display white balance
);
float[] displayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
@@ -278,8 +245,8 @@ public final class ColorDisplayService extends SystemService {
mCurrentColorTemperatureXYZ = ColorSpace.cctToIlluminantdXyz(cct);
mChromaticAdaptationMatrix =
- ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
- mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+ ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+ mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
// Convert the adaptation matrix to RGB space
float[] result = ColorSpace.mul3x3(mChromaticAdaptationMatrix,
@@ -386,7 +353,7 @@ public final class ColorDisplayService extends SystemService {
float saturation = saturationLevel * 0.1f;
float desaturation = 1.0f - saturation;
float[] luminance = {0.231f * desaturation, 0.715f * desaturation,
- 0.072f * desaturation};
+ 0.072f * desaturation};
mMatrixGlobalSaturation[0] = luminance[0] + saturation;
mMatrixGlobalSaturation[1] = luminance[0];
mMatrixGlobalSaturation[2] = luminance[0];
@@ -421,7 +388,7 @@ public final class ColorDisplayService extends SystemService {
* subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's
* ProgramCache for full implementation details.
*/
- private static final float[] MATRIX_INVERT_COLOR = new float[] {
+ private static final float[] MATRIX_INVERT_COLOR = new float[]{
0.402f, -0.598f, -0.599f, 0f,
-1.174f, -0.174f, -1.175f, 0f,
-0.228f, -0.228f, 0.772f, 0f,
@@ -445,7 +412,7 @@ public final class ColorDisplayService extends SystemService {
public ColorDisplayService(Context context) {
super(context);
- mHandler = new TintHandler(Looper.getMainLooper());
+ mHandler = new TintHandler(DisplayThread.get().getLooper());
}
@Override
@@ -548,23 +515,30 @@ public final class ColorDisplayService extends SystemService {
if (setting != null) {
switch (setting) {
case Secure.NIGHT_DISPLAY_ACTIVATED:
- onNightDisplayActivated(mNightDisplayController.isActivated());
+ final boolean activated = isNightDisplayActivatedSetting();
+ if (mNightDisplayTintController.isActivatedStateNotSet()
+ || mNightDisplayTintController.isActivated() != activated) {
+ mNightDisplayTintController.onActivated(activated);
+ }
break;
case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
- onNightDisplayColorTemperatureChanged(
- mNightDisplayController.getColorTemperature());
+ final int temperature = getNightDisplayColorTemperatureSetting();
+ if (mNightDisplayTintController.getColorTemperature()
+ != temperature) {
+ mNightDisplayTintController
+ .onColorTemperatureChanged(temperature);
+ }
break;
case Secure.NIGHT_DISPLAY_AUTO_MODE:
- onNightDisplayAutoModeChanged(
- mNightDisplayController.getAutoMode());
+ onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
break;
case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME:
onNightDisplayCustomStartTimeChanged(
- mNightDisplayController.getCustomStartTime());
+ getNightDisplayCustomStartTimeInternal().getLocalTime());
break;
case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
onNightDisplayCustomEndTimeChanged(
- mNightDisplayController.getCustomEndTime());
+ getNightDisplayCustomEndTimeInternal().getLocalTime());
break;
case System.DISPLAY_COLOR_MODE:
onDisplayColorModeChanged(mNightDisplayController.getColorMode());
@@ -624,14 +598,14 @@ public final class ColorDisplayService extends SystemService {
// Prepare the night display color transformation matrix.
mNightDisplayTintController
.setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
- mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature());
+ mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting());
// Initialize the current auto mode.
- onNightDisplayAutoModeChanged(mNightDisplayController.getAutoMode());
+ onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
- // Force the initialization current activated state.
+ // Force the initialization of the current saved activation state.
if (mNightDisplayTintController.isActivatedStateNotSet()) {
- onNightDisplayActivated(mNightDisplayController.isActivated());
+ mNightDisplayTintController.onActivated(isNightDisplayActivatedSetting());
}
}
@@ -665,23 +639,6 @@ public final class ColorDisplayService extends SystemService {
}
}
- private void onNightDisplayActivated(boolean activated) {
- if (mNightDisplayTintController.isActivatedStateNotSet()
- || mNightDisplayTintController.isActivated() != activated) {
- Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
-
- mNightDisplayTintController.setActivated(activated);
-
- if (mNightDisplayAutoMode != null) {
- mNightDisplayAutoMode.onActivated(activated);
- }
-
- updateDisplayWhiteBalanceStatus();
-
- applyTint(mNightDisplayTintController, false);
- }
- }
-
private void onNightDisplayAutoModeChanged(int autoMode) {
Slog.d(TAG, "onNightDisplayAutoModeChanged: autoMode=" + autoMode);
@@ -690,9 +647,9 @@ public final class ColorDisplayService extends SystemService {
mNightDisplayAutoMode = null;
}
- if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
+ if (autoMode == AUTO_MODE_CUSTOM_TIME) {
mNightDisplayAutoMode = new CustomNightDisplayAutoMode();
- } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
+ } else if (autoMode == AUTO_MODE_TWILIGHT) {
mNightDisplayAutoMode = new TwilightNightDisplayAutoMode();
}
@@ -717,13 +674,8 @@ public final class ColorDisplayService extends SystemService {
}
}
- private void onNightDisplayColorTemperatureChanged(int colorTemperature) {
- mNightDisplayTintController.setMatrix(colorTemperature);
- applyTint(mNightDisplayTintController, true);
- }
-
private void onDisplayColorModeChanged(int mode) {
- if (mode == -1) {
+ if (mode == NOT_SET) {
return;
}
@@ -732,7 +684,7 @@ public final class ColorDisplayService extends SystemService {
mNightDisplayTintController
.setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
- mNightDisplayTintController.setMatrix(mNightDisplayController.getColorTemperature());
+ mNightDisplayTintController.setMatrix(getNightDisplayColorTemperatureSetting());
updateDisplayWhiteBalanceStatus();
@@ -901,6 +853,71 @@ public final class ColorDisplayService extends SystemService {
return availabilityFlags;
}
+ private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) {
+ if (getNightDisplayAutoModeInternal() != autoMode) {
+ Secure.putStringForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+ null,
+ mCurrentUser);
+ }
+ return Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser);
+ }
+
+ private int getNightDisplayAutoModeInternal() {
+ int autoMode = getNightDisplayAutoModeRawInternal();
+ if (autoMode == NOT_SET) {
+ autoMode = getContext().getResources().getInteger(
+ R.integer.config_defaultNightDisplayAutoMode);
+ }
+ if (autoMode != AUTO_MODE_DISABLED
+ && autoMode != AUTO_MODE_CUSTOM_TIME
+ && autoMode != AUTO_MODE_TWILIGHT) {
+ Slog.e(TAG, "Invalid autoMode: " + autoMode);
+ autoMode = AUTO_MODE_DISABLED;
+ }
+ return autoMode;
+ }
+
+ private int getNightDisplayAutoModeRawInternal() {
+ return Secure
+ .getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,
+ NOT_SET, mCurrentUser);
+ }
+
+ private Time getNightDisplayCustomStartTimeInternal() {
+ int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser);
+ if (startTimeValue == NOT_SET) {
+ startTimeValue = getContext().getResources().getInteger(
+ R.integer.config_defaultNightDisplayCustomStartTime);
+ }
+ return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000));
+ }
+
+ private boolean setNightDisplayCustomStartTimeInternal(Time startTime) {
+ return Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
+ startTime.getLocalTime().toSecondOfDay() * 1000,
+ mCurrentUser);
+ }
+
+ private Time getNightDisplayCustomEndTimeInternal() {
+ int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser);
+ if (endTimeValue == NOT_SET) {
+ endTimeValue = getContext().getResources().getInteger(
+ R.integer.config_defaultNightDisplayCustomEndTime);
+ }
+ return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000));
+ }
+
+ private boolean setNightDisplayCustomEndTimeInternal(Time endTime) {
+ return Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000,
+ mCurrentUser);
+ }
+
/**
* Returns the last time the night display transform activation state was changed, or {@link
* LocalDateTime#MIN} if night display has never been activated.
@@ -930,6 +947,89 @@ public final class ColorDisplayService extends SystemService {
.setSaturationLevel(packageName, mCurrentUser, saturationLevel);
}
+ private void setColorModeInternal(@ColorMode int colorMode) {
+ if (!isColorModeAvailable(colorMode)) {
+ throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
+ }
+ System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE,
+ colorMode,
+ mCurrentUser);
+ }
+
+ private @ColorMode
+ int getColorModeInternal() {
+ final ContentResolver cr = getContext().getContentResolver();
+ if (Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ 0, mCurrentUser) == 1
+ || Secure.getIntForUser(cr, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ 0, mCurrentUser) == 1) {
+ // There are restrictions on the available color modes combined with a11y transforms.
+ if (isColorModeAvailable(COLOR_MODE_SATURATED)) {
+ return COLOR_MODE_SATURATED;
+ } else if (isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+ return COLOR_MODE_AUTOMATIC;
+ }
+ }
+
+ int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser);
+ if (colorMode == -1) {
+ // There might be a system property controlling color mode that we need to respect; if
+ // not, this will set a suitable default.
+ colorMode = getCurrentColorModeFromSystemProperties();
+ }
+
+ // This happens when a color mode is no longer available (e.g., after system update or B&R)
+ // or the device does not support any color mode.
+ if (!isColorModeAvailable(colorMode)) {
+ if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
+ colorMode = COLOR_MODE_NATURAL;
+ } else if (colorMode == COLOR_MODE_SATURATED
+ && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+ colorMode = COLOR_MODE_AUTOMATIC;
+ } else if (colorMode == COLOR_MODE_AUTOMATIC
+ && isColorModeAvailable(COLOR_MODE_SATURATED)) {
+ colorMode = COLOR_MODE_SATURATED;
+ } else {
+ colorMode = -1;
+ }
+ }
+
+ return colorMode;
+ }
+
+ /**
+ * Get the current color mode from system properties, or return -1 if invalid.
+ *
+ * See {@link com.android.server.display.DisplayTransformManager}
+ */
+ private @ColorMode
+ int getCurrentColorModeFromSystemProperties() {
+ final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
+ if (displayColorSetting == 0) {
+ return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
+ ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+ } else if (displayColorSetting == 1) {
+ return COLOR_MODE_SATURATED;
+ } else if (displayColorSetting == 2) {
+ return COLOR_MODE_AUTOMATIC;
+ } else {
+ return -1;
+ }
+ }
+
+ private boolean isColorModeAvailable(@ColorMode int colorMode) {
+ final int[] availableColorModes = getContext().getResources().getIntArray(
+ R.array.config_availableColorModes);
+ if (availableColorModes != null) {
+ for (int mode : availableColorModes) {
+ if (mode == colorMode) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)");
pw.println("Night Display:");
@@ -941,6 +1041,33 @@ public final class ColorDisplayService extends SystemService {
mAppSaturationController.dump(pw);
}
+ private boolean isNightDisplayActivatedSetting() {
+ return Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1;
+ }
+
+ private int getNightDisplayColorTemperatureSetting() {
+ return clampNightDisplayColorTemperature(Secure.getIntForUser(
+ getContext().getContentResolver(), Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, NOT_SET,
+ mCurrentUser));
+ }
+
+ private int clampNightDisplayColorTemperature(int colorTemperature) {
+ if (colorTemperature == NOT_SET) {
+ colorTemperature = getContext().getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureDefault);
+ }
+ final int minimumTemperature = ColorDisplayManager.getMinimumColorTemperature(getContext());
+ final int maximumTemperature = ColorDisplayManager.getMaximumColorTemperature(getContext());
+ if (colorTemperature < minimumTemperature) {
+ colorTemperature = minimumTemperature;
+ } else if (colorTemperature > maximumTemperature) {
+ colorTemperature = maximumTemperature;
+ }
+
+ return colorTemperature;
+ }
+
private abstract class NightDisplayAutoMode {
public abstract void onActivated(boolean activated);
@@ -987,13 +1114,13 @@ public final class ColorDisplayService extends SystemService {
// Maintain the existing activated state if within the current period.
if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
&& (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
- activate = mNightDisplayController.isActivated();
+ activate = isNightDisplayActivatedSetting();
}
}
if (mNightDisplayTintController.isActivatedStateNotSet() || (
mNightDisplayTintController.isActivated() != activate)) {
- mNightDisplayController.setActivated(activate);
+ mNightDisplayTintController.setActivated(activate);
}
updateNextAlarm(mNightDisplayTintController.isActivated(), now);
@@ -1014,8 +1141,8 @@ public final class ColorDisplayService extends SystemService {
intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
- mStartTime = mNightDisplayController.getCustomStartTime();
- mEndTime = mNightDisplayController.getCustomEndTime();
+ mStartTime = getNightDisplayCustomStartTimeInternal().getLocalTime();
+ mEndTime = getNightDisplayCustomEndTimeInternal().getLocalTime();
mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
@@ -1083,13 +1210,13 @@ public final class ColorDisplayService extends SystemService {
// Maintain the existing activated state if within the current period.
if (mLastActivatedTime.isBefore(now) && (mLastActivatedTime.isBefore(sunrise)
^ mLastActivatedTime.isBefore(sunset))) {
- activate = mNightDisplayController.isActivated();
+ activate = isNightDisplayActivatedSetting();
}
}
if (mNightDisplayTintController.isActivatedStateNotSet() || (
mNightDisplayTintController.isActivated() != activate)) {
- mNightDisplayController.setActivated(activate);
+ mNightDisplayTintController.setActivated(activate);
}
}
@@ -1211,6 +1338,114 @@ public final class ColorDisplayService extends SystemService {
public abstract int getLevel();
}
+ private final class NightDisplayTintController extends TintController {
+
+ private float[] mMatrix = new float[16];
+ private final float[] mColorTempCoefficients = new float[9];
+ private Integer mColorTemp;
+
+ /**
+ * Set coefficients based on whether the color matrix is linear or not.
+ */
+ @Override
+ public void setUp(Context context, boolean needsLinear) {
+ final String[] coefficients = context.getResources().getStringArray(needsLinear
+ ? R.array.config_nightDisplayColorTemperatureCoefficients
+ : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
+ for (int i = 0; i < 9 && i < coefficients.length; i++) {
+ mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
+ }
+ }
+
+ @Override
+ public void setMatrix(int cct) {
+ if (mMatrix.length != 16) {
+ Slog.d(TAG, "The display transformation matrix must be 4x4");
+ return;
+ }
+
+ Matrix.setIdentityM(mMatrix, 0);
+
+ final float squareTemperature = cct * cct;
+ final float red = squareTemperature * mColorTempCoefficients[0]
+ + cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
+ final float green = squareTemperature * mColorTempCoefficients[3]
+ + cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
+ final float blue = squareTemperature * mColorTempCoefficients[6]
+ + cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
+ mMatrix[0] = red;
+ mMatrix[5] = green;
+ mMatrix[10] = blue;
+ }
+
+ @Override
+ public float[] getMatrix() {
+ return isActivated() ? mMatrix : MATRIX_IDENTITY;
+ }
+
+ @Override
+ public void setActivated(Boolean activated) {
+ if (activated == null) {
+ super.setActivated(null);
+ return;
+ }
+
+ boolean activationStateChanged = activated != isActivated();
+
+ if (!isActivatedStateNotSet() && activationStateChanged) {
+ // This is a true state change, so set this as the last activation time.
+ Secure.putStringForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+ LocalDateTime.now().toString(),
+ mCurrentUser);
+ }
+
+ if (isActivatedStateNotSet() || activationStateChanged) {
+ super.setActivated(activated);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_ACTIVATED,
+ activated ? 1 : 0, mCurrentUser);
+ onActivated(activated);
+ }
+ }
+
+ @Override
+ public int getLevel() {
+ return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
+ }
+
+ void onActivated(boolean activated) {
+ Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
+ if (mNightDisplayAutoMode != null) {
+ mNightDisplayAutoMode.onActivated(activated);
+ }
+
+ if (ColorDisplayManager.isDisplayWhiteBalanceAvailable(getContext())) {
+ updateDisplayWhiteBalanceStatus();
+ }
+
+ mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED);
+ }
+
+ int getColorTemperature() {
+ return mColorTemp != null ? clampNightDisplayColorTemperature(mColorTemp)
+ : getNightDisplayColorTemperatureSetting();
+ }
+
+ boolean setColorTemperature(int temperature) {
+ mColorTemp = temperature;
+ final boolean success = Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser);
+ onColorTemperatureChanged(temperature);
+ return success;
+ }
+
+ void onColorTemperatureChanged(int temperature) {
+ setMatrix(temperature);
+ mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE);
+ }
+ }
+
/**
* Local service that allows color transforms to be enabled from other system services.
*/
@@ -1270,7 +1505,7 @@ public final class ColorDisplayService extends SystemService {
private final class TintHandler extends Handler {
- TintHandler(Looper looper) {
+ private TintHandler(Looper looper) {
super(looper, null, true /* async */);
}
@@ -1281,6 +1516,12 @@ public final class ColorDisplayService extends SystemService {
mGlobalSaturationTintController.setMatrix(msg.arg1);
applyTint(mGlobalSaturationTintController, false);
break;
+ case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE:
+ applyTint(mNightDisplayTintController, true);
+ break;
+ case MSG_APPLY_NIGHT_DISPLAY_ANIMATED:
+ applyTint(mNightDisplayTintController, false);
+ break;
}
}
}
@@ -1290,11 +1531,37 @@ public final class ColorDisplayService extends SystemService {
*/
public interface ColorTransformController {
- /** Apply the given saturation (grayscale) matrix to the associated AppWindow. */
+ /**
+ * Apply the given saturation (grayscale) matrix to the associated AppWindow.
+ */
void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation);
}
- private final class BinderService extends IColorDisplayManager.Stub {
+ @VisibleForTesting
+ final class BinderService extends IColorDisplayManager.Stub {
+
+ @Override
+ public void setColorMode(int colorMode) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set display color mode");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setColorModeInternal(colorMode);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public int getColorMode() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getColorModeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
@Override
public boolean isDeviceColorManaged() {
@@ -1354,8 +1621,139 @@ public final class ColorDisplayService extends SystemService {
}
@Override
+ public boolean setNightDisplayActivated(boolean activated) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set night display activated");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mNightDisplayTintController.setActivated(activated);
+ return true;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isNightDisplayActivated() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mNightDisplayTintController.isActivated();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setNightDisplayColorTemperature(int temperature) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set night display temperature");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mNightDisplayTintController.setColorTemperature(temperature);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public int getNightDisplayColorTemperature() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mNightDisplayTintController.getColorTemperature();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setNightDisplayAutoMode(int autoMode) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set night display auto mode");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return setNightDisplayAutoModeInternal(autoMode);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public int getNightDisplayAutoMode() {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to get night display auto mode");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getNightDisplayAutoModeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public int getNightDisplayAutoModeRaw() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getNightDisplayAutoModeRawInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setNightDisplayCustomStartTime(Time startTime) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set night display custom start time");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return setNightDisplayCustomStartTimeInternal(startTime);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public Time getNightDisplayCustomStartTime() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getNightDisplayCustomStartTimeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean setNightDisplayCustomEndTime(Time endTime) {
+ getContext().enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+ "Permission required to set night display custom end time");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return setNightDisplayCustomEndTimeInternal(endTime);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public Time getNightDisplayCustomEndTime() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getNightDisplayCustomEndTimeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return;
+ }
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cb3f91b7b6bb..b89768ac4b68 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -36,6 +36,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.ColorSpace;
import android.graphics.Point;
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
@@ -93,9 +94,9 @@ import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
-import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -294,6 +295,7 @@ public final class DisplayManagerService extends SystemService {
// is rejected by the system.
private final Curve mMinimumBrightnessCurve;
private final Spline mMinimumBrightnessSpline;
+ private final ColorSpace mWideColorSpace;
public DisplayManagerService(Context context) {
this(context, new Injector());
@@ -322,6 +324,8 @@ public final class DisplayManagerService extends SystemService {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
mCurrentUserId = UserHandle.USER_SYSTEM;
+ ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
+ mWideColorSpace = colorSpaces[1];
}
public void setupSchedulerPolicies() {
@@ -1073,6 +1077,10 @@ public final class DisplayManagerService extends SystemService {
return mMinimumBrightnessCurve;
}
+ int getPreferredWideGamutColorSpaceIdInternal() {
+ return mWideColorSpace.getId();
+ }
+
private void setBrightnessConfigurationForUserInternal(
@Nullable BrightnessConfiguration c, @UserIdInt int userId,
@Nullable String packageName) {
@@ -2128,6 +2136,16 @@ public final class DisplayManagerService extends SystemService {
}
}
+ @Override // Binder call
+ public int getPreferredWideGamutColorSpaceId() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getPreferredWideGamutColorSpaceIdInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
void setBrightness(int brightness) {
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT);
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index a5e9728e4b68..b1b7d3c8769b 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import android.app.ActivityTaskManager;
+import android.hardware.display.ColorDisplayManager;
import android.opengl.Matrix;
import android.os.IBinder;
import android.os.Parcel;
@@ -27,7 +28,6 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.ColorDisplayController;
import java.util.Arrays;
@@ -248,20 +248,20 @@ public class DisplayTransformManager {
* work in linear space.
*/
public static boolean needsLinearColorMatrix(int colorMode) {
- return colorMode != ColorDisplayController.COLOR_MODE_SATURATED;
+ return colorMode != ColorDisplayManager.COLOR_MODE_SATURATED;
}
public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) {
- if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
+ if (colorMode == ColorDisplayManager.COLOR_MODE_NATURAL) {
applySaturation(COLOR_SATURATION_NATURAL);
setDisplayColor(DISPLAY_COLOR_MANAGED);
- } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
+ } else if (colorMode == ColorDisplayManager.COLOR_MODE_BOOSTED) {
applySaturation(COLOR_SATURATION_BOOSTED);
setDisplayColor(DISPLAY_COLOR_MANAGED);
- } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
+ } else if (colorMode == ColorDisplayManager.COLOR_MODE_SATURATED) {
applySaturation(COLOR_SATURATION_NATURAL);
setDisplayColor(DISPLAY_COLOR_UNMANAGED);
- } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
+ } else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
applySaturation(COLOR_SATURATION_NATURAL);
setDisplayColor(DISPLAY_COLOR_ENHANCED);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3fd3945fc87d..4db541c29448 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -288,6 +288,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private static final class DebugFlags {
static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
new DebugFlag("debug.optimize_startinput", false);
+ static final DebugFlag FLAG_PRE_RENDER_IME_VIEWS =
+ new DebugFlag("persist.pre_render_ime_views", false);
}
@UserIdInt
@@ -304,6 +306,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final boolean mHasFeature;
private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
new ArrayMap<>();
+ private final boolean mIsLowRam;
private final HardKeyboardListener mHardKeyboardListener;
private final AppOpsManager mAppOpsManager;
private final UserManager mUserManager;
@@ -403,6 +406,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final ClientDeathRecipient clientDeathRecipient;
boolean sessionRequested;
+ // Determines if IMEs should be pre-rendered.
+ // DebugFlag can be flipped anytime. This flag is kept per-client to maintain behavior
+ // through the life of the current client.
+ boolean shouldPreRenderIme;
SessionState curSession;
@Override
@@ -615,6 +622,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
* <dd>
* If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
* </dd>
+ * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+ * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
+ * currently invisible.
+ * </dd>
* </dl>
* <em>Do not update this value outside of setImeWindowStatus.</em>
*/
@@ -1361,6 +1372,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
mHardKeyboardBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_externalHardKeyboardBehavior);
+ mIsLowRam = ActivityManager.isLowRamDeviceStatic();
Bundle extras = new Bundle();
extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
@@ -1393,7 +1405,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
- mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
+ mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady);
updateCurrentProfileIds();
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
@@ -1682,7 +1694,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
methodList);
final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
- mContext.getContentResolver(), methodMap, methodList, userId, true);
+ mContext.getContentResolver(), methodMap, userId, true);
return settings.getEnabledInputMethodListLocked();
}
@@ -1738,7 +1750,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return Collections.emptyList();
}
final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
- mContext.getContentResolver(), methodMap, methodList, userId, true);
+ mContext.getContentResolver(), methodMap, userId, true);
return settings.getEnabledInputMethodSubtypeListLocked(
mContext, imi, allowsImplicitlySelectedSubtypes);
}
@@ -2264,7 +2276,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (mSwitchingDialog != null) return false;
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
&& mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
- if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
+ if ((visibility & InputMethodService.IME_ACTIVE) == 0
+ || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+ return false;
+ }
if (mWindowManagerInternal.isHardKeyboardAvailable()) {
if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
// When physical keyboard is attached, we show the ime switcher (or notification if
@@ -2372,6 +2387,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (mCurToken == null) {
return;
}
+ if (DEBUG) {
+ Slog.d(TAG, "IME window vis: " + vis
+ + " active: " + (vis & InputMethodService.IME_ACTIVE)
+ + " inv: " + (vis & InputMethodService.IME_INVISIBLE));
+ }
+
// TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
// all updateSystemUi happens on system previlege.
final long ident = Binder.clearCallingIdentity();
@@ -2830,6 +2851,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
switchUserLocked(userId);
}
+ // Master feature flag that overrides other conditions and forces IME preRendering.
+ if (DEBUG) {
+ Slog.v(TAG, "IME PreRendering MASTER flag: "
+ + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value()
+ + ", LowRam: " + mIsLowRam);
+ }
+ // pre-rendering not supported on low-ram devices.
+ cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam;
if (mCurFocusedWindow == windowToken) {
if (DEBUG) {
@@ -3493,7 +3522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
try {
setEnabledSessionInMainThread(session);
session.method.startInput(startInputToken, inputContext, missingMethods,
- editorInfo, restarting);
+ editorInfo, restarting, session.client.shouldPreRenderIme);
} catch (RemoteException e) {
}
args.recycle();
@@ -4421,6 +4450,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@ShellCommandResult
private int refreshDebugProperties() {
DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+ DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh();
return ShellCommandResult.SUCCESS;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 88d1a9c0eb6c..326984c7202c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -34,6 +34,7 @@ import android.os.UserManagerInternal;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Printer;
@@ -66,9 +67,9 @@ import java.util.Locale;
*/
final class InputMethodUtils {
public static final boolean DEBUG = false;
- public static final int NOT_A_SUBTYPE_ID = -1;
- public static final String SUBTYPE_MODE_ANY = null;
- public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+ static final int NOT_A_SUBTYPE_ID = -1;
+ private static final String SUBTYPE_MODE_ANY = null;
+ static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
private static final String TAG = "InputMethodUtils";
private static final Locale ENGLISH_LOCALE = new Locale("en");
private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
@@ -108,7 +109,7 @@ final class InputMethodUtils {
// ----------------------------------------------------------------------
// Utilities for debug
- public static String getApiCallStack() {
+ static String getApiCallStack() {
String apiCallStack = "";
try {
throw new RuntimeException();
@@ -131,10 +132,9 @@ final class InputMethodUtils {
}
// ----------------------------------------------------------------------
- public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
- final Context context, final boolean checkDefaultAttribute,
- @Nullable final Locale requiredLocale, final boolean checkCountry,
- final String requiredSubtypeMode) {
+ private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
+ boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
+ String requiredSubtypeMode) {
if (!imi.isSystem()) {
return false;
}
@@ -148,8 +148,8 @@ final class InputMethodUtils {
}
@Nullable
- public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
- final Context context) {
+ private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+ Context context) {
// At first, find the fallback locale from the IMEs that are declared as "default" in the
// current locale. Note that IME developers can declare an IME as "default" only for
// some particular locales but "not default" for other locales.
@@ -177,8 +177,8 @@ final class InputMethodUtils {
return null;
}
- private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi,
- final Context context, final boolean checkDefaultAttribute) {
+ private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
+ Context context, boolean checkDefaultAttribute) {
if (!imi.isSystem()) {
return false;
}
@@ -198,7 +198,7 @@ final class InputMethodUtils {
return false;
}
- public static Locale getSystemLocaleFromContext(final Context context) {
+ private static Locale getSystemLocaleFromContext(Context context) {
try {
return context.getResources().getConfiguration().locale;
} catch (Resources.NotFoundException ex) {
@@ -212,10 +212,9 @@ final class InputMethodUtils {
@NonNull
private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
- public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis,
- final Context context, final boolean checkDefaultAttribute,
- @Nullable final Locale locale, final boolean checkCountry,
- final String requiredSubtypeMode) {
+ InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+ boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
+ String requiredSubtypeMode) {
for (int i = 0; i < imis.size(); ++i) {
final InputMethodInfo imi = imis.get(i);
if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
@@ -228,8 +227,7 @@ final class InputMethodUtils {
// TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
// documented more clearly.
- public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis,
- final Context context) {
+ InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
// If one or more auxiliary input methods are available, OK to stop populating the list.
for (final InputMethodInfo imi : mInputMethodSet) {
if (imi.isAuxiliaryIme()) {
@@ -269,8 +267,8 @@ final class InputMethodUtils {
}
private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
- final ArrayList<InputMethodInfo> imis, final Context context,
- @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
+ ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+ @Nullable Locale fallbackLocale) {
// Once the system becomes ready, we pick up at least one keyboard in the following order.
// Secondary users fall into this category in general.
// 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
@@ -317,7 +315,7 @@ final class InputMethodUtils {
return builder;
}
- public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+ static ArrayList<InputMethodInfo> getDefaultEnabledImes(
Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
// We will primarily rely on the system locale, but also keep relying on the fallback locale
@@ -336,13 +334,13 @@ final class InputMethodUtils {
return builder.build();
}
- public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+ static ArrayList<InputMethodInfo> getDefaultEnabledImes(
Context context, ArrayList<InputMethodInfo> imis) {
return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
}
- public static boolean containsSubtypeOf(final InputMethodInfo imi,
- @Nullable final Locale locale, final boolean checkCountry, final String mode) {
+ static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
+ boolean checkCountry, String mode) {
if (locale == null) {
return false;
}
@@ -371,7 +369,7 @@ final class InputMethodUtils {
return false;
}
- public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+ static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
@@ -380,7 +378,7 @@ final class InputMethodUtils {
return subtypes;
}
- public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
+ static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
if (enabledImes == null || enabledImes.isEmpty()) {
return null;
}
@@ -404,11 +402,11 @@ final class InputMethodUtils {
return enabledImes.get(Math.max(firstFoundSystemIme, 0));
}
- public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
+ static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
}
- public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+ static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
if (imi != null) {
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
@@ -430,7 +428,7 @@ final class InputMethodUtils {
};
@VisibleForTesting
- public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
Resources res, InputMethodInfo imi) {
final LocaleList systemLocales = res.getConfiguration().getLocales();
@@ -564,7 +562,7 @@ final class InputMethodUtils {
* it will return the first subtype matched with mode
* @return the most applicable subtypeId
*/
- public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+ static InputMethodSubtype findLastResortApplicableSubtypeLocked(
Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
boolean canIgnoreLocaleAsLastResort) {
if (subtypes == null || subtypes.size() == 0) {
@@ -615,14 +613,13 @@ final class InputMethodUtils {
return applicableSubtype;
}
- public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
+ static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
if (subtype == null) return true;
return !subtype.isAuxiliary();
}
- public static void setNonSelectedSystemImesDisabledUntilUsed(
- IPackageManager packageManager, List<InputMethodInfo> enabledImis,
- int userId, String callingPackage) {
+ static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager,
+ List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) {
if (DEBUG) {
Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
}
@@ -710,7 +707,7 @@ final class InputMethodUtils {
}
}
- public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
+ static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
InputMethodSubtype subtype) {
final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
return subtype != null
@@ -730,8 +727,8 @@ final class InputMethodUtils {
* @param packageName the package name.
* @return {@code true} if the package name belongs to the UID.
*/
- public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
- final int uid, final String packageName) {
+ static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager,
+ @UserIdInt int uid, String packageName) {
try {
appOpsManager.checkPackage(uid, packageName);
return true;
@@ -760,6 +757,14 @@ final class InputMethodUtils {
*/
private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
+ private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
+ }
+
+ private static final UserManagerInternal sUserManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+
private boolean mCopyOnWrite = false;
@NonNull
private String mEnabledInputMethodsStrCache = "";
@@ -802,10 +807,9 @@ final class InputMethodUtils {
return imsList;
}
- public InputMethodSettings(
- Resources res, ContentResolver resolver,
- ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
- @UserIdInt int userId, boolean copyOnWrite) {
+ InputMethodSettings(Resources res, ContentResolver resolver,
+ ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
+ boolean copyOnWrite) {
mRes = res;
mResolver = resolver;
mMethodMap = methodMap;
@@ -820,7 +824,7 @@ final class InputMethodUtils {
* (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
* settings on the {@link Settings.Secure} until we do the first write operation.
*/
- public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
+ void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
if (DEBUG) {
Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
}
@@ -834,16 +838,18 @@ final class InputMethodUtils {
// TODO: mCurrentProfileIds should be updated here.
}
- private void putString(@NonNull final String key, @Nullable final String str) {
+ private void putString(@NonNull String key, @Nullable String str) {
if (mCopyOnWrite) {
mCopyOnWriteDataStore.put(key, str);
} else {
- Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
+ final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+ ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+ Settings.Secure.putStringForUser(mResolver, key, str, userId);
}
}
@Nullable
- private String getString(@NonNull final String key, @Nullable final String defaultValue) {
+ private String getString(@NonNull String key, @Nullable String defaultValue) {
final String result;
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
result = mCopyOnWriteDataStore.get(key);
@@ -853,15 +859,17 @@ final class InputMethodUtils {
return result != null ? result : defaultValue;
}
- private void putInt(final String key, final int value) {
+ private void putInt(String key, int value) {
if (mCopyOnWrite) {
mCopyOnWriteDataStore.put(key, String.valueOf(value));
} else {
- Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
+ final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+ ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+ Settings.Secure.putIntForUser(mResolver, key, value, userId);
}
}
- private int getInt(final String key, final int defaultValue) {
+ private int getInt(String key, int defaultValue) {
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
final String result = mCopyOnWriteDataStore.get(key);
return result != null ? Integer.parseInt(result) : 0;
@@ -869,11 +877,11 @@ final class InputMethodUtils {
return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
}
- private void putBoolean(final String key, final boolean value) {
+ private void putBoolean(String key, boolean value) {
putInt(key, value ? 1 : 0);
}
- private boolean getBoolean(final String key, final boolean defaultValue) {
+ private boolean getBoolean(String key, boolean defaultValue) {
return getInt(key, defaultValue ? 1 : 0) == 1;
}
@@ -893,12 +901,12 @@ final class InputMethodUtils {
}
}
- public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
+ ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
}
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
List<InputMethodSubtype> enabledSubtypes =
getEnabledInputMethodSubtypeListLocked(imi);
@@ -909,8 +917,7 @@ final class InputMethodUtils {
return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
}
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
- InputMethodInfo imi) {
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
List<Pair<String, ArrayList<String>>> imsList =
getEnabledInputMethodsAndSubtypeListLocked();
ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
@@ -934,13 +941,13 @@ final class InputMethodUtils {
return enabledSubtypes;
}
- public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
mInputMethodSplitter,
mSubtypeSplitter);
}
- public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+ void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
if (reloadInputMethodStr) {
getEnabledInputMethodsStr();
}
@@ -957,7 +964,7 @@ final class InputMethodUtils {
* Build and put a string of EnabledInputMethods with removing specified Id.
* @return the specified id was removed or not.
*/
- public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
boolean isRemoved = false;
boolean needsAppendSeparator = false;
@@ -1012,7 +1019,7 @@ final class InputMethodUtils {
}
@NonNull
- public String getEnabledInputMethodsStr() {
+ String getEnabledInputMethodsStr() {
mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
if (DEBUG) {
Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
@@ -1080,12 +1087,12 @@ final class InputMethodUtils {
}
}
- public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ Pair<String, String> getLastInputMethodAndSubtypeLocked() {
// Gets the first one from the history
return getLastSubtypeForInputMethodLockedInternal(null);
}
- public String getLastSubtypeForInputMethodLocked(String imeId) {
+ String getLastSubtypeForInputMethodLocked(String imeId) {
Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
if (ime != null) {
return ime.second;
@@ -1203,7 +1210,7 @@ final class InputMethodUtils {
return history;
}
- public void putSelectedInputMethod(String imeId) {
+ void putSelectedInputMethod(String imeId) {
if (DEBUG) {
Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+ mCurrentUserId);
@@ -1211,7 +1218,7 @@ final class InputMethodUtils {
putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
}
- public void putSelectedSubtype(int subtypeId) {
+ void putSelectedSubtype(int subtypeId) {
if (DEBUG) {
Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+ mCurrentUserId);
@@ -1220,7 +1227,7 @@ final class InputMethodUtils {
}
@Nullable
- public String getSelectedInputMethod() {
+ String getSelectedInputMethod() {
final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
if (DEBUG) {
Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
@@ -1228,7 +1235,7 @@ final class InputMethodUtils {
return imi;
}
- public boolean isSubtypeSelected() {
+ boolean isSubtypeSelected() {
return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
}
@@ -1236,11 +1243,11 @@ final class InputMethodUtils {
return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
}
- public boolean isShowImeWithHardKeyboardEnabled() {
+ boolean isShowImeWithHardKeyboardEnabled() {
return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
}
- public void setShowImeWithHardKeyboard(boolean show) {
+ void setShowImeWithHardKeyboard(boolean show) {
putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
}
@@ -1249,7 +1256,7 @@ final class InputMethodUtils {
return mCurrentUserId;
}
- public int getSelectedInputMethodSubtypeId(String selectedImiId) {
+ int getSelectedInputMethodSubtypeId(String selectedImiId) {
final InputMethodInfo imi = mMethodMap.get(selectedImiId);
if (imi == null) {
return NOT_A_SUBTYPE_ID;
@@ -1258,8 +1265,8 @@ final class InputMethodUtils {
return getSubtypeIdFromHashCode(imi, subtypeHashCode);
}
- public void saveCurrentInputMethodAndSubtypeToHistory(
- String curMethodId, InputMethodSubtype currentSubtype) {
+ void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+ InputMethodSubtype currentSubtype) {
String subtypeId = NOT_A_SUBTYPE_ID_STR;
if (currentSubtype != null) {
subtypeId = String.valueOf(currentSubtype.hashCode());
@@ -1277,8 +1284,8 @@ final class InputMethodUtils {
}
}
- public static boolean isSoftInputModeStateVisibleAllowed(
- int targetSdkVersion, @StartInputFlags int startInputFlags) {
+ static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
+ @StartInputFlags int startInputFlags) {
if (targetSdkVersion < Build.VERSION_CODES.P) {
// for compatibility.
return true;
diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java
index 4d9b5f5d01c6..bec1947df228 100644
--- a/services/core/java/com/android/server/job/JobConcurrencyManager.java
+++ b/services/core/java/com/android/server/job/JobConcurrencyManager.java
@@ -36,6 +36,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.StatLogger;
import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.StateController;
@@ -148,14 +149,14 @@ class JobConcurrencyManager {
Slog.d(TAG, "Interactive: " + interactive);
}
- final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
if (interactive) {
- mLastScreenOnRealtime = now;
+ mLastScreenOnRealtime = nowRealtime;
mEffectiveInteractiveState = true;
mHandler.removeCallbacks(mRampUpForScreenOff);
} else {
- mLastScreenOffRealtime = now;
+ mLastScreenOffRealtime = nowRealtime;
// Set mEffectiveInteractiveState to false after the delay, when we may increase
// the concurrency.
@@ -232,38 +233,24 @@ class JobConcurrencyManager {
private void updateMaxCountsLocked() {
refreshSystemStateLocked();
- if (mEffectiveInteractiveState) {
- // Screen on
- switch (mLastMemoryTrimLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_MODERATE;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_LOW;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_CRITICAL;
- break;
- default:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_ON_NORMAL;
- break;
- }
- } else {
- // Screen off
- switch (mLastMemoryTrimLevel) {
- case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_MODERATE;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_LOW:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_LOW;
- break;
- case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_CRITICAL;
- break;
- default:
- mMaxJobCounts = mConstants.MAX_JOB_COUNTS_OFF_NORMAL;
- break;
- }
+ final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
+ ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
+ : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
+
+
+ switch (mLastMemoryTrimLevel) {
+ case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+ mMaxJobCounts = jobCounts.moderate;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ mMaxJobCounts = jobCounts.low;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ mMaxJobCounts = jobCounts.critical;
+ break;
+ default:
+ mMaxJobCounts = jobCounts.normal;
+ break;
}
}
@@ -303,7 +290,7 @@ class JobConcurrencyManager {
// Initialize the work variables and also count running jobs.
mJobCountTracker.reset(
- mMaxJobCounts.getTotalMax(),
+ mMaxJobCounts.getMaxTotal(),
mMaxJobCounts.getMaxBg(),
mMaxJobCounts.getMinBg());
@@ -482,10 +469,7 @@ class JobConcurrencyManager {
}
- public void dumpLocked(IndentingPrintWriter pw) {
- final long now = System.currentTimeMillis();
- final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
-
+ public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
pw.println("Concurrency:");
pw.increaseIndent();
@@ -522,19 +506,36 @@ class JobConcurrencyManager {
}
}
- public void dumpProtoLocked(ProtoOutputStream proto) {
- // TODO Implement it.
+ public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
+ final long token = proto.start(tag);
+
+ proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE,
+ mCurrentInteractiveState);
+ proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE,
+ mEffectiveInteractiveState);
+
+ proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
+ nowRealtime - mLastScreenOnRealtime);
+ proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
+ nowRealtime - mLastScreenOffRealtime);
+
+ mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
+
+ proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL,
+ mLastMemoryTrimLevel);
+
+ proto.end(token);
}
/**
- * This class decides, taking into account {@link #mMaxJobCounts} and how many jos are running /
+ * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
* pending, how many more job can start.
*
* Extracted for testing and logging.
*/
@VisibleForTesting
static class JobCountTracker {
- private int mConfigNumTotalMaxJobs;
+ private int mConfigNumMaxTotalJobs;
private int mConfigNumMaxBgJobs;
private int mConfigNumMinBgJobs;
@@ -552,7 +553,7 @@ class JobConcurrencyManager {
private int mNumActualMaxBgJobs;
void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
- mConfigNumTotalMaxJobs = numTotalMaxJobs;
+ mConfigNumMaxTotalJobs = numTotalMaxJobs;
mConfigNumMaxBgJobs = numMaxBgJobs;
mConfigNumMinBgJobs = numMinBgJobs;
@@ -607,12 +608,12 @@ class JobConcurrencyManager {
// However, if there are FG jobs already running, we have to adjust it.
mNumReservedForBg = Math.min(reservedForBg,
- mConfigNumTotalMaxJobs - mNumRunningFgJobs);
+ mConfigNumMaxTotalJobs - mNumRunningFgJobs);
// Max FG is [total - [number needed for BG jobs]]
// [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
final int maxFg =
- mConfigNumTotalMaxJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
+ mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
// The above maxFg is the theoretical max. If there are less FG jobs, the actual
// max FG will be lower accordingly.
@@ -623,7 +624,7 @@ class JobConcurrencyManager {
// Max BG is [total - actual max FG], but cap at [config max BG].
final int maxBg = Math.min(
mConfigNumMaxBgJobs,
- mConfigNumTotalMaxJobs - mNumActualMaxFgJobs);
+ mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
// If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
// This isn't needed for the logic to work, but this will give consistent output
@@ -669,12 +670,13 @@ class JobConcurrencyManager {
final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
return String.format(
"Config={tot=%d bg min/max=%d/%d}"
- + " Running: %d / %d (%d)"
+ + " Running[FG/BG (total)]: %d / %d (%d)"
+ " Pending: %d / %d (%d)"
+ " Actual max: %d%s / %d%s (%d%s)"
+ + " Res BG: %d"
+ " Starting: %d / %d (%d)"
+ " Total: %d%s / %d%s (%d%s)",
- mConfigNumTotalMaxJobs,
+ mConfigNumMaxTotalJobs,
mConfigNumMinBgJobs,
mConfigNumMaxBgJobs,
@@ -684,19 +686,37 @@ class JobConcurrencyManager {
mNumPendingFgJobs, mNumPendingBgJobs,
mNumPendingFgJobs + mNumPendingBgJobs,
- mNumActualMaxFgJobs, (totalFg <= mConfigNumTotalMaxJobs) ? "" : "*",
+ mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
mNumActualMaxFgJobs + mNumActualMaxBgJobs,
- (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumTotalMaxJobs)
+ (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
? "" : "*",
+ mNumReservedForBg,
+
mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
- totalFg + totalBg, (totalFg + totalBg <= mConfigNumTotalMaxJobs) ? "" : "*"
+ totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
);
}
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
+ proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
+ proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
+
+ proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
+ proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
+
+ proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
+ proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
+
+ proto.end(token);
+ }
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index bd12075fdad3..7625aafd0907 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -366,14 +366,20 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- public int getTotalMax() {
+ /** Total number of jobs to run simultaneously. */
+ public int getMaxTotal() {
return mTotal.getValue();
}
+ /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
public int getMaxBg() {
return mMaxBg.getValue();
}
+ /**
+ * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
+ * pending, rather than always running the TOTAL number of FG jobs.
+ */
public int getMinBg() {
return mMinBg.getValue();
}
@@ -384,10 +390,39 @@ public class JobSchedulerService extends com.android.server.SystemService
mMinBg.dump(pw, prefix);
}
- public void dumpProto(ProtoOutputStream proto, long tagTotal, long tagBg) {
- mTotal.dumpProto(proto, tagTotal);
- mMaxBg.dumpProto(proto, tagBg);
- mMinBg.dumpProto(proto, tagBg);
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
+ mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
+ mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
+ proto.end(token);
+ }
+ }
+
+ /** {@link MaxJobCounts} for each memory trim level. */
+ static class MaxJobCountsPerMemoryTrimLevel {
+ public final MaxJobCounts normal;
+ public final MaxJobCounts moderate;
+ public final MaxJobCounts low;
+ public final MaxJobCounts critical;
+
+ MaxJobCountsPerMemoryTrimLevel(
+ MaxJobCounts normal,
+ MaxJobCounts moderate, MaxJobCounts low,
+ MaxJobCounts critical) {
+ this.normal = normal;
+ this.moderate = moderate;
+ this.low = low;
+ this.critical = critical;
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
+ moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
+ low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
+ critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
+ proto.end(token);
}
}
@@ -546,45 +581,44 @@ public class JobSchedulerService extends com.android.server.SystemService
float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
// Max job counts for screen on / off, for each memory trim level.
- final MaxJobCounts MAX_JOB_COUNTS_ON_NORMAL = new MaxJobCounts(
- 8, "max_job_total_on_normal",
- 6, "max_job_max_bg_on_normal",
- 2, "max_job_min_bg_on_normal");
-
- final MaxJobCounts MAX_JOB_COUNTS_ON_MODERATE = new MaxJobCounts(
- 8, "max_job_total_on_moderate",
- 4, "max_job_max_bg_on_moderate",
- 2, "max_job_min_bg_on_moderate");
-
- final MaxJobCounts MAX_JOB_COUNTS_ON_LOW = new MaxJobCounts(
- 5, "max_job_total_on_low",
- 1, "max_job_max_bg_on_low",
- 1, "max_job_min_bg_on_low");
-
- final MaxJobCounts MAX_JOB_COUNTS_ON_CRITICAL = new MaxJobCounts(
- 5, "max_job_total_on_critical",
- 1, "max_job_max_bg_on_critical",
- 1, "max_job_min_bg_on_critical");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_NORMAL = new MaxJobCounts(
- 10, "max_job_total_off_normal",
- 6, "max_job_max_bg_off_normal",
- 2, "max_job_min_bg_off_normal");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_MODERATE = new MaxJobCounts(
- 10, "max_job_total_off_moderate",
- 4, "max_job_max_bg_off_moderate",
- 2, "max_job_min_bg_off_moderate");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_LOW = new MaxJobCounts(
- 5, "max_job_total_off_low",
- 1, "max_job_max_bg_off_low",
- 1, "max_job_min_bg_off_low");
-
- final MaxJobCounts MAX_JOB_COUNTS_OFF_CRITICAL = new MaxJobCounts(
- 5, "max_job_total_off_critical",
- 1, "max_job_max_bg_off_critical",
- 1, "max_job_min_bg_off_critical");
+ final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
+ new MaxJobCountsPerMemoryTrimLevel(
+ new MaxJobCounts(
+ 8, "max_job_total_on_normal",
+ 6, "max_job_max_bg_on_normal",
+ 2, "max_job_min_bg_on_normal"),
+ new MaxJobCounts(
+ 8, "max_job_total_on_moderate",
+ 4, "max_job_max_bg_on_moderate",
+ 2, "max_job_min_bg_on_moderate"),
+ new MaxJobCounts(
+ 5, "max_job_total_on_low",
+ 1, "max_job_max_bg_on_low",
+ 1, "max_job_min_bg_on_low"),
+ new MaxJobCounts(
+ 5, "max_job_total_on_critical",
+ 1, "max_job_max_bg_on_critical",
+ 1, "max_job_min_bg_on_critical"));
+
+ final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
+ new MaxJobCountsPerMemoryTrimLevel(
+ new MaxJobCounts(
+ 10, "max_job_total_off_normal",
+ 6, "max_job_max_bg_off_normal",
+ 2, "max_job_min_bg_off_normal"),
+ new MaxJobCounts(
+ 10, "max_job_total_off_moderate",
+ 4, "max_job_max_bg_off_moderate",
+ 2, "max_job_min_bg_off_moderate"),
+ new MaxJobCounts(
+ 5, "max_job_total_off_low",
+ 1, "max_job_max_bg_off_low",
+ 1, "max_job_min_bg_off_low"),
+ new MaxJobCounts(
+ 5, "max_job_total_off_critical",
+ 1, "max_job_max_bg_off_critical",
+ 1, "max_job_min_bg_off_critical"));
+
/** Wait for this long after screen off before increasing the job concurrency. */
final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
@@ -766,15 +800,15 @@ public class JobSchedulerService extends com.android.server.SystemService
MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
DEFAULT_MODERATE_USE_FACTOR);
- MAX_JOB_COUNTS_ON_NORMAL.parse(mParser);
- MAX_JOB_COUNTS_ON_MODERATE.parse(mParser);
- MAX_JOB_COUNTS_ON_LOW.parse(mParser);
- MAX_JOB_COUNTS_ON_CRITICAL.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
- MAX_JOB_COUNTS_OFF_NORMAL.parse(mParser);
- MAX_JOB_COUNTS_OFF_MODERATE.parse(mParser);
- MAX_JOB_COUNTS_OFF_LOW.parse(mParser);
- MAX_JOB_COUNTS_OFF_CRITICAL.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
+ MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
@@ -851,15 +885,17 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
- MAX_JOB_COUNTS_ON_NORMAL.dump(pw, "");
- MAX_JOB_COUNTS_ON_MODERATE.dump(pw, "");
- MAX_JOB_COUNTS_ON_LOW.dump(pw, "");
- MAX_JOB_COUNTS_ON_CRITICAL.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
+
+ MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
+ MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
- MAX_JOB_COUNTS_OFF_NORMAL.dump(pw, "");
- MAX_JOB_COUNTS_OFF_MODERATE.dump(pw, "");
- MAX_JOB_COUNTS_OFF_LOW.dump(pw, "");
- MAX_JOB_COUNTS_OFF_CRITICAL.dump(pw, "");
+ SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
@@ -917,7 +953,11 @@ public class JobSchedulerService extends com.android.server.SystemService
proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
- // TODO Dump max job counts.
+ MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
+ MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
+
+ SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
+ ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
@@ -3371,8 +3411,10 @@ public class JobSchedulerService extends com.android.server.SystemService
void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
final int filterUidFinal = UserHandle.getAppId(filterUid);
+ final long now = sSystemClock.millis();
final long nowElapsed = sElapsedRealtimeClock.millis();
final long nowUptime = sUptimeMillisClock.millis();
+
final Predicate<JobStatus> predicate = (js) -> {
return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
|| UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
@@ -3548,7 +3590,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
pw.println();
- mConcurrencyManager.dumpLocked(pw);
+ mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
pw.println();
pw.print("PersistStats: ");
@@ -3560,6 +3602,7 @@ public class JobSchedulerService extends com.android.server.SystemService
void dumpInternalProto(final FileDescriptor fd, int filterUid) {
ProtoOutputStream proto = new ProtoOutputStream(fd);
final int filterUidFinal = UserHandle.getAppId(filterUid);
+ final long now = sSystemClock.millis();
final long nowElapsed = sElapsedRealtimeClock.millis();
final long nowUptime = sUptimeMillisClock.millis();
final Predicate<JobStatus> predicate = (js) -> {
@@ -3703,7 +3746,8 @@ public class JobSchedulerService extends com.android.server.SystemService
proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
}
- mConcurrencyManager.dumpProtoLocked(proto);
+ mConcurrencyManager.dumpProtoLocked(proto,
+ JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed);
}
proto.flush();
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index f1de37188510..e6f0ed9d14b0 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -20,8 +20,6 @@ import android.content.Context;
import android.location.Address;
import android.location.GeocoderParams;
import android.location.IGeocodeProvider;
-import android.os.RemoteException;
-import android.util.Log;
import com.android.internal.os.BackgroundThread;
import com.android.server.ServiceWatcher;
@@ -68,35 +66,22 @@ public class GeocoderProxy {
public String getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
- final String[] result = new String[]{"Service not Available"};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
- try {
- result[0] = provider.getFromLocation(
- latitude, longitude, maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return result[0];
+ return provider.getFromLocation(latitude, longitude, maxResults, params, addrs);
+ }, "Service not Available");
}
public String getFromLocationName(String locationName,
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, List<Address> addrs) {
- final String[] result = new String[]{"Service not Available"};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
- try {
- result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude,
- lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
- maxResults, params, addrs);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return result[0];
+ return provider.getFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+ maxResults, params, addrs);
+ }, "Service not Available");
}
}
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index 591889dde5de..ca9c0e030b4e 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -19,9 +19,15 @@ package com.android.server.location;
import android.annotation.SuppressLint;
import android.app.AppOpsManager;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -55,6 +61,8 @@ class GnssVisibilityControl {
private static final String LOCATION_PERMISSION_NAME =
"android.permission.ACCESS_FINE_LOCATION";
+ private static final String[] NO_LOCATION_ENABLED_PROXY_APPS = new String[0];
+
// Wakelocks
private static final String WAKELOCK_KEY = TAG;
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
@@ -66,13 +74,16 @@ class GnssVisibilityControl {
private final Handler mHandler;
private final Context mContext;
+ private boolean mIsMasterLocationSettingsEnabled = true;
+ private boolean mIsOnRoamingNetwork = false;
+
// Number of non-framework location access proxy apps is expected to be small (< 5).
private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>(
HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS);
private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
- uid -> postEvent(() -> handlePermissionsChanged(uid));
+ uid -> runOnHandler(() -> handlePermissionsChanged(uid));
GnssVisibilityControl(Context context, Looper looper) {
mContext = context;
@@ -81,8 +92,15 @@ class GnssVisibilityControl {
mHandler = new Handler(looper);
mAppOps = mContext.getSystemService(AppOpsManager.class);
mPackageManager = mContext.getPackageManager();
+
+ // Set to empty proxy app list initially until the configuration properties are loaded.
+ updateNfwLocationAccessProxyAppsInGnssHal();
+
+ // Listen for proxy app package installation, removal events.
+ listenForProxyAppsPackageUpdates();
+ listenForRoamingNetworkUpdate();
+
// TODO(b/122855984): Handle global location settings on/off.
- // TODO(b/122856189): Handle roaming case.
}
void updateProxyApps(List<String> nfwLocationAccessProxyApps) {
@@ -90,18 +108,68 @@ class GnssVisibilityControl {
// but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling
// so that the order of processing is preserved. GnssLocationProvider should
// first load the new config parameters for the new SIM and then call this method.
- postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps));
+ runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps));
+ }
+
+ void masterLocationSettingsUpdated(boolean enabled) {
+ runOnHandler(() -> handleMasterLocationSettingsUpdated(enabled));
}
void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
boolean inEmergencyMode, boolean isCachedLocation) {
- postEvent(() -> handleNfwNotification(
+ runOnHandler(() -> handleNfwNotification(
new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName,
requestor, requestorId, responseType, inEmergencyMode, isCachedLocation)));
}
- private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) {
+ private void listenForProxyAppsPackageUpdates() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
+ switch (action) {
+ case Intent.ACTION_PACKAGE_ADDED:
+ case Intent.ACTION_PACKAGE_REMOVED:
+ case Intent.ACTION_PACKAGE_REPLACED:
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ handleProxyAppPackageUpdate(pkgName, action);
+ break;
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, mHandler);
+ }
+
+ private void handleProxyAppPackageUpdate(String pkgName, String action) {
+ final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName);
+ // pkgName is not one of the proxy apps in our list.
+ if (locationPermission == null) {
+ return;
+ }
+
+ Log.i(TAG, "Proxy app " + pkgName + " package changed: " + action);
+ final boolean updatedLocationPermission = hasLocationPermission(pkgName);
+ if (locationPermission != updatedLocationPermission) {
+ // Permission changed. So, update the GNSS HAL with the updated list.
+ mProxyAppToLocationPermissions.put(pkgName, updatedLocationPermission);
+ updateNfwLocationAccessProxyAppsInGnssHal();
+ }
+ }
+
+ private void handleUpdateProxyApps(List<String> nfwLocationAccessProxyApps) {
+ if (!isProxyAppListUpdated(nfwLocationAccessProxyApps)) {
+ return;
+ }
+
if (nfwLocationAccessProxyApps.isEmpty()) {
// Stop listening for app permission changes. Clear the app list in GNSS HAL.
if (!mProxyAppToLocationPermissions.isEmpty()) {
@@ -125,6 +193,27 @@ class GnssVisibilityControl {
updateNfwLocationAccessProxyAppsInGnssHal();
}
+ private boolean isProxyAppListUpdated(List<String> nfwLocationAccessProxyApps) {
+ if (nfwLocationAccessProxyApps.size() != mProxyAppToLocationPermissions.size()) {
+ return true;
+ }
+
+ for (String nfwLocationAccessProxyApp : nfwLocationAccessProxyApps) {
+ if (!mProxyAppToLocationPermissions.containsKey(nfwLocationAccessProxyApp)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void handleMasterLocationSettingsUpdated(boolean enabled) {
+ mIsMasterLocationSettingsEnabled = enabled;
+ Log.i(TAG, "Master location settings switch changed to "
+ + (enabled ? "enabled" : "disabled"));
+ updateNfwLocationAccessProxyAppsInGnssHal();
+ }
+
// Represents NfwNotification structure in IGnssVisibilityControlCallback.hal
private static class NfwNotification {
private static final String KEY_PROTOCOL_STACK = "ProtocolStack";
@@ -149,7 +238,7 @@ class GnssVisibilityControl {
private final boolean mInEmergencyMode;
private final boolean mIsCachedLocation;
- NfwNotification(String proxyAppPackageName, byte protocolStack,
+ private NfwNotification(String proxyAppPackageName, byte protocolStack,
String otherProtocolStackName, byte requestor, String requestorId,
byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
mProxyAppPackageName = proxyAppPackageName;
@@ -162,7 +251,7 @@ class GnssVisibilityControl {
mIsCachedLocation = isCachedLocation;
}
- void copyFieldsToIntent(Intent intent) {
+ private void copyFieldsToIntent(Intent intent) {
intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack);
if (!TextUtils.isEmpty(mOtherProtocolStackName)) {
intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName);
@@ -188,7 +277,7 @@ class GnssVisibilityControl {
mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation);
}
- String getResponseTypeAsString() {
+ private String getResponseTypeAsString() {
switch (mResponseType) {
case NFW_RESPONSE_TYPE_REJECTED:
return "REJECTED";
@@ -246,6 +335,24 @@ class GnssVisibilityControl {
}
private void updateNfwLocationAccessProxyAppsInGnssHal() {
+ final String[] locationPermissionEnabledProxyApps = shouldDisableNfwLocationAccess()
+ ? NO_LOCATION_ENABLED_PROXY_APPS : getLocationPermissionEnabledProxyApps();
+ final String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
+ Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
+ + proxyAppsStr);
+ boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
+ if (!result) {
+ Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
+ + " GNSS HAL to: " + proxyAppsStr);
+ }
+ }
+
+ private boolean shouldDisableNfwLocationAccess() {
+ // TODO(b/122856189): Add disableWhenRoaming configuration per proxy app.
+ return mIsOnRoamingNetwork || !mIsMasterLocationSettingsEnabled;
+ }
+
+ private String[] getLocationPermissionEnabledProxyApps() {
// Get a count of proxy apps with location permission enabled to array creation size.
int countLocationPermissionEnabledProxyApps = 0;
for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) {
@@ -264,15 +371,7 @@ class GnssVisibilityControl {
locationPermissionEnabledProxyApps[i++] = proxyApp;
}
}
-
- String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
- Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
- + proxyAppsStr);
- boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
- if (!result) {
- Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
- + " GNSS HAL to: " + proxyAppsStr);
- }
+ return locationPermissionEnabledProxyApps;
}
private void handleNfwNotification(NfwNotification nfwNotification) {
@@ -360,7 +459,31 @@ class GnssVisibilityControl {
isPermissionMismatched);
}
- private void postEvent(Runnable event) {
+ private void listenForRoamingNetworkUpdate() {
+ // Register for network capabilities changes to monitor roaming changes.
+ ConnectivityManager mConnMgr = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addCapability(NetworkCapabilities.TRANSPORT_CELLULAR);
+ NetworkRequest networkRequest = networkRequestBuilder.build();
+ mConnMgr.registerNetworkCallback(networkRequest,
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(Network network,
+ NetworkCapabilities capabilities) {
+ boolean isRoaming = !capabilities.hasTransport(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+ // No locking required for this test and set because the callback
+ // runs in mHandler thread.
+ if (mIsOnRoamingNetwork != isRoaming) {
+ mIsOnRoamingNetwork = isRoaming;
+ updateNfwLocationAccessProxyAppsInGnssHal();
+ }
+ }
+ }, mHandler);
+ }
+
+ private void runOnHandler(Runnable event) {
// Hold a wake lock until this message is delivered.
// Note that this assumes the message will not be removed from the queue before
// it is handled (otherwise the wake lock would be leaked).
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index a6da8c5e7713..6b5b1bebd20f 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -127,20 +127,16 @@ public class LocationProviderProxy extends AbstractLocationProvider {
return mServiceWatcher.start();
}
- private void initializeService(IBinder binder) {
+ private void initializeService(IBinder binder) throws RemoteException {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
- try {
- service.setLocationProviderManager(mManager);
+ service.setLocationProviderManager(mManager);
- synchronized (mRequestLock) {
- if (mRequest != null) {
- service.setRequest(mRequest, mWorkSource);
- }
+ synchronized (mRequestLock) {
+ if (mRequest != null) {
+ service.setRequest(mRequest, mWorkSource);
}
- } catch (RemoteException e) {
- Log.w(TAG, e);
}
}
@@ -157,63 +153,44 @@ public class LocationProviderProxy extends AbstractLocationProvider {
}
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- service.setRequest(request, source);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
+ service.setRequest(request, source);
});
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" service=" + mServiceWatcher);
- mServiceWatcher.runOnBinder(binder -> {
+ mServiceWatcher.runOnBinderBlocking(binder -> {
try {
TransferPipe.dumpAsync(binder, fd, args);
} catch (IOException | RemoteException e) {
- pw.println(" failed to dump location provider: " + e);
+ pw.println(" failed to dump location provider");
}
- });
+ return null;
+ }, null);
}
@Override
public int getStatus(Bundle extras) {
- int[] status = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- status[0] = service.getStatus(extras);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return status[0];
+ return service.getStatus(extras);
+ }, LocationProvider.TEMPORARILY_UNAVAILABLE);
}
@Override
public long getStatusUpdateTime() {
- long[] updateTime = new long[] {0L};
- mServiceWatcher.runOnBinder(binder -> {
+ return mServiceWatcher.runOnBinderBlocking(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- updateTime[0] = service.getStatusUpdateTime();
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- });
- return updateTime[0];
+ return service.getStatusUpdateTime();
+ }, 0L);
}
@Override
public void sendExtraCommand(String command, Bundle extras) {
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- try {
- service.sendExtraCommand(command, extras);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
+ service.sendExtraCommand(command, extras);
});
}
}
diff --git a/services/core/java/com/android/server/location/OWNERS b/services/core/java/com/android/server/location/OWNERS
index 92b4d5fea113..c2c95e6042de 100644
--- a/services/core/java/com/android/server/location/OWNERS
+++ b/services/core/java/com/android/server/location/OWNERS
@@ -1,6 +1,8 @@
+aadmal@google.com
arthuri@google.com
bduddie@google.com
gomo@google.com
sooniln@google.com
weiwa@google.com
wyattriley@google.com
+yuhany@google.com
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index 1541b1d520cd..dd26a29d55af 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -42,8 +42,6 @@ import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.media.session.ControllerLink;
import android.media.session.IActiveSessionsListener;
@@ -60,7 +58,6 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
@@ -1007,17 +1004,50 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl {
if (DEBUG) {
Log.d(TAG, "Session2 is created " + sessionToken);
}
+ if (pid != sessionToken.getPid()) {
+ throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid
+ + " but actually=" + sessionToken.getPid());
+ }
+ if (uid != sessionToken.getUid()) {
+ throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+ + " but actually=" + sessionToken.getUid());
+ }
+ int userId = UserHandle.getUserId(uid);
+ List<Session2Token> session2Tokens = mSession2TokensPerUser.get(userId);
+ if (session2Tokens.contains(sessionToken)) {
+ if (DEBUG) {
+ Log.d(TAG, "notifySession2Created(): Ignoring already existing token "
+ + sessionToken);
+ }
+ return;
+ }
+ session2Tokens.add(sessionToken);
+ pushSession2TokensChangedLocked(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Session2 is destroyed " + sessionToken);
+ }
+ if (pid != sessionToken.getPid()) {
+ throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid
+ + " but actually=" + sessionToken.getPid());
+ }
if (uid != sessionToken.getUid()) {
throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+ " but actually=" + sessionToken.getUid());
}
- Controller2Callback callback = new Controller2Callback(sessionToken);
- // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
- // it's closed.
- // TODO: Keep controller as well for better readability
- // because the GC behavior isn't straightforward.
- MediaController2 controller = new MediaController2(mContext, sessionToken,
- new HandlerExecutor(mHandler), callback);
+ int userId = UserHandle.getUserId(uid);
+ mSession2TokensPerUser.get(userId).remove(sessionToken);
+ pushSession2TokensChangedLocked(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2114,30 +2144,4 @@ public class MediaSessionServiceImpl extends MediaSessionService.ServiceImpl {
obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
}
}
-
- private class Controller2Callback extends MediaController2.ControllerCallback {
- private final Session2Token mToken;
-
- Controller2Callback(Session2Token token) {
- mToken = token;
- }
-
- @Override
- public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mToken.getUid());
- mSession2TokensPerUser.get(userId).add(mToken);
- pushSession2TokensChangedLocked(userId);
- }
- }
-
- @Override
- public void onDisconnected(MediaController2 controller) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mToken.getUid());
- mSession2TokensPerUser.get(userId).remove(mToken);
- pushSession2TokensChangedLocked(userId);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a1646862de9f..de3f50ad8c2c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1087,7 +1087,8 @@ public class NotificationManagerService extends SystemService {
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
|| action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
- || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
+ || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
+ || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_ALL);
String pkgList[] = null;
@@ -1108,6 +1109,23 @@ public class NotificationManagerService extends SystemService {
uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
cancelNotifications = false;
unhideNotifications = true;
+ } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
+ final int distractionRestrictions =
+ intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS,
+ PackageManager.RESTRICTION_NONE);
+ if ((distractionRestrictions
+ & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ cancelNotifications = false;
+ hideNotifications = true;
+ } else {
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ cancelNotifications = false;
+ unhideNotifications = true;
+ }
+
} else if (queryRestart) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1651,6 +1669,7 @@ public class NotificationManagerService extends SystemService {
IntentFilter suspendedPkgFilter = new IntentFilter();
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
suspendedPkgFilter, null, null);
@@ -7743,6 +7762,20 @@ public class NotificationManagerService extends SystemService {
mPackageIntentReceiver.onReceive(getContext(), intent);
}
+ @VisibleForTesting
+ protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
+ // only use for testing: mimic receive broadcast that package is (un)distracting
+ // but does not actually register that info with packagemanager
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
+
+ final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+ intent.putExtras(extras);
+
+ mPackageIntentReceiver.onReceive(getContext(), intent);
+ }
+
/**
* Wrapper for a StatusBarNotification object that allows transfer across a oneway
* binder without sending large amounts of data over a oneway transaction.
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 02fc51f89e62..ac965faedd34 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -690,6 +690,8 @@ public final class NotificationRecord {
importance));
}
}
+ // We have now gotten all the information out of the adjustments and can forget them.
+ mAdjustments.clear();
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 3d88f20f0710..2aaa1edcfad9 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -176,6 +176,14 @@ public class NotificationShellCmd extends ShellCommand {
// only use for testing
mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired());
}
+ case "distract_package": {
+ // only use for testing
+ // Flag values are in
+ // {@link android.content.pm.PackageManager.DistractionRestriction}.
+ mDirectService.simulatePackageDistractionBroadcast(
+ Integer.parseInt(getNextArgRequired()),
+ getNextArgRequired().split(","));
+ }
break;
case "post":
case "notify":
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 65fc9824c76e..ad9ac1232437 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -50,6 +50,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* {@hide}
@@ -57,7 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class BackgroundDexOptService extends JobService {
private static final String TAG = "BackgroundDexOptService";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int JOB_IDLE_OPTIMIZE = 800;
private static final int JOB_POST_BOOT_UPDATE = 801;
@@ -102,7 +103,6 @@ public class BackgroundDexOptService extends JobService {
private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
private final File mDataDir = Environment.getDataDirectory();
-
private static final long mDowngradeUnusedAppsThresholdInMillis =
getDowngradeUnusedAppsThresholdInMillis();
@@ -275,21 +275,18 @@ public class BackgroundDexOptService extends JobService {
long lowStorageThreshold = getLowStorageThreshold(context);
// Optimize primary apks.
- int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
- sFailedPackageNamesPrimary);
-
+ int result = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ true);
if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return result;
}
-
- if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+ if (supportSecondaryDex()) {
result = reconcileSecondaryDexFiles(pm.getDexManager());
if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return result;
}
-
- result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
- sFailedPackageNamesSecondary);
+ result = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ false);
}
return result;
}
@@ -339,92 +336,84 @@ public class BackgroundDexOptService extends JobService {
}
private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold, boolean is_for_primary_dex,
- ArraySet<String> failedPackageNames) {
+ long lowStorageThreshold, boolean isForPrimaryDex) {
ArraySet<String> updatedPackages = new ArraySet<>();
Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+ Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
// Only downgrade apps when space is low on device.
// Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
// up disk before user hits the actual lowStorageThreshold.
final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
lowStorageThreshold;
boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
+ Log.d(TAG, "Should Downgrade " + shouldDowngrade);
+ boolean dex_opt_performed = false;
for (String pkg : pkgs) {
int abort_code = abortIdleOptimizations(lowStorageThreshold);
if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return abort_code;
}
-
- synchronized (failedPackageNames) {
- if (failedPackageNames.contains(pkg)) {
- // Skip previously failing package
- continue;
- }
- }
-
- int reason;
- boolean downgrade;
- long package_size_before = 0; //used when the app is downgraded
// Downgrade unused packages.
if (unusedPackages.contains(pkg) && shouldDowngrade) {
- package_size_before = getPackageSize(pm, pkg);
- // This applies for system apps or if packages location is not a directory, i.e.
- // monolithic install.
- if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
- // For apps that don't have the oat directory, instead of downgrading,
- // remove their compiler artifacts from dalvik cache.
- pm.deleteOatArtifactsOfPackage(pkg);
+ dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex);
+ } else {
+ if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
+ // can't dexopt because of low space.
continue;
- } else {
- reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
- downgrade = true;
}
- } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
- reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- downgrade = false;
- } else {
- // can't dexopt because of low space.
- continue;
+ dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
}
-
- synchronized (failedPackageNames) {
- // Conservatively add package to the list of failing ones in case
- // performDexOpt never returns.
- failedPackageNames.add(pkg);
+ if (dex_opt_performed) {
+ updatedPackages.add(pkg);
}
+ }
- // Optimize package if needed. Note that there can be no race between
- // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- boolean success;
- int dexoptFlags =
- DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
- DexoptOptions.DEXOPT_BOOT_COMPLETE |
- (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
- DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
- if (is_for_primary_dex) {
- int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
- dexoptFlags));
- success = result != PackageDexOptimizer.DEX_OPT_FAILED;
- if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
- }
+ notifyPinService(updatedPackages);
+ return OPTIMIZE_PROCESSED;
+ }
+
+
+ /**
+ * Try to downgrade the package to a smaller compilation filter.
+ * eg. if the package is in speed-profile the package will be downgraded to verify.
+ * @param pm PackageManagerService
+ * @param pkg The package to be downgraded.
+ * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+ * @return true if the package was downgraded.
+ */
+ private boolean downgradePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex) {
+ Log.d(TAG, "Downgrading " + pkg);
+ boolean dex_opt_performed = false;
+ int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
+ | DexoptOptions.DEXOPT_DOWNGRADE;
+ long package_size_before = getPackageSize(pm, pkg);
+
+ if (isForPrimaryDex) {
+ // This applies for system apps or if packages location is not a directory, i.e.
+ // monolithic install.
+ if (!pm.canHaveOatDir(pkg)) {
+ // For apps that don't have the oat directory, instead of downgrading,
+ // remove their compiler artifacts from dalvik cache.
+ pm.deleteOatArtifactsOfPackage(pkg);
} else {
- success = pm.performDexOpt(new DexoptOptions(pkg,
- reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
- }
- if (success) {
- // Dexopt succeeded, remove package from the list of failing ones.
- synchronized (failedPackageNames) {
- failedPackageNames.remove(pkg);
- }
- if (downgrade) {
- StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
- getPackageSize(pm, pkg), /*aggressive=*/ false);
- }
+ dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
}
+ } else {
+ dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
}
- notifyPinService(updatedPackages);
- return OPTIMIZE_PROCESSED;
+
+ if (dex_opt_performed) {
+ StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
+ getPackageSize(pm, pkg), /*aggressive=*/ false);
+ }
+ return dex_opt_performed;
+ }
+
+ private boolean supportSecondaryDex() {
+ return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
}
private int reconcileSecondaryDexFiles(DexManager dm) {
@@ -438,6 +427,73 @@ public class BackgroundDexOptService extends JobService {
return OPTIMIZE_PROCESSED;
}
+ /**
+ *
+ * Optimize package if needed. Note that there can be no race between
+ * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
+ * @param pm An instance of PackageManagerService
+ * @param pkg The package to be downgraded.
+ * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+ * @return true if the package was downgraded.
+ */
+ private boolean optimizePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex) {
+ int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+ int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+
+ return isForPrimaryDex
+ ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
+ : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ }
+
+ private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+ int dexoptFlags) {
+ int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+ () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ }
+
+ private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+ int dexoptFlags) {
+ DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
+ dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+ int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+ () -> pm.performDexOpt(dexoptOptions)
+ ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
+ );
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ }
+
+ /**
+ * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
+ * the package is added to the list of failed packages.
+ * Return one of following result:
+ * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+ * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+ * {@link PackageDexOptimizer#DEX_OPT_FAILED}
+ */
+ private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
+ Supplier<Integer> performDexOptWrapper) {
+ ArraySet<String> sFailedPackageNames =
+ isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
+ synchronized (sFailedPackageNames) {
+ if (sFailedPackageNames.contains(pkg)) {
+ // Skip previously failing package
+ return PackageDexOptimizer.DEX_OPT_SKIPPED;
+ }
+ sFailedPackageNames.add(pkg);
+ }
+ int result = performDexOptWrapper.get();
+ if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+ synchronized (sFailedPackageNames) {
+ sFailedPackageNames.remove(pkg);
+ }
+ }
+ return result;
+ }
+
// Evaluate whether or not idle optimizations should continue.
private int abortIdleOptimizations(long lowStorageThreshold) {
if (mAbortIdleOptimization.get()) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a33f14bab4b1..d0ef4f1523d4 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -25,6 +25,7 @@ import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -135,6 +136,7 @@ public class LauncherAppsService extends SystemService {
private final Context mContext;
private final UserManager mUm;
private final UserManagerInternal mUserManagerInternal;
+ private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,6 +158,8 @@ public class LauncherAppsService extends SystemService {
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mUserManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(UserManagerInternal.class));
+ mUsageStatsManagerInternal = Preconditions.checkNotNull(
+ LocalServices.getService(UsageStatsManagerInternal.class));
mActivityManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(ActivityManagerInternal.class));
mActivityTaskManagerInternal = Preconditions.checkNotNull(
@@ -671,6 +675,30 @@ public class LauncherAppsService extends SystemService {
}
}
+ @Override
+ public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage,
+ String packageName, UserHandle user) {
+ verifyCallingPackage(callingPackage);
+ if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
+ return null;
+ }
+
+ final PackageManagerInternal pmi =
+ LocalServices.getService(PackageManagerInternal.class);
+ final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier());
+ if (!cn.getPackageName().equals(callingPackage)) {
+ throw new SecurityException("Caller is not the active launcher");
+ }
+
+ final UsageStatsManagerInternal.AppUsageLimitData data =
+ mUsageStatsManagerInternal.getAppUsageLimit(packageName, user);
+ if (data == null) {
+ return null;
+ }
+ return new LauncherApps.AppUsageLimit(
+ data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining());
+ }
+
private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5412e9425adf..94b1b362f43a 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -506,8 +506,10 @@ public class PackageDexOptimizer {
boolean isUsedByOtherApps) {
int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
- // When a priv app is configured to run out of box, only verify it.
- if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
+ // When an app or priv app is configured to run out of box, only verify it.
+ if (info.isCodeIntegrityPreferred()
+ || (info.isPrivilegedApp()
+ && DexManager.isPackageSelectedToRunOob(info.packageName))) {
return "verify";
}
if (vmSafeMode) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0ab2a7361ac0..eab5c8f866a8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -537,7 +537,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
- false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR);
+ false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR,
+ "");
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index b8825bbd2d72..494ec3ff67aa 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -155,6 +155,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_IS_FAILED = "isFailed";
private static final String ATTR_IS_APPLIED = "isApplied";
private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
+ private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage";
private static final String ATTR_MODE = "mode";
private static final String ATTR_INSTALL_FLAGS = "installFlags";
private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -267,6 +268,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private boolean mStagedSessionFailed;
@GuardedBy("mLock")
private int mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+ @GuardedBy("mLock")
+ private String mStagedSessionErrorMessage;
/**
* Path to the validated base APK for this session, which may point at an
@@ -413,7 +416,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
File stageDir, String stageCid, boolean prepared, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
- boolean isFailed, boolean isApplied, int stagedSessionErrorCode) {
+ boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
+ String stagedSessionErrorMessage) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -447,6 +451,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStagedSessionFailed = isFailed;
mStagedSessionApplied = isApplied;
mStagedSessionErrorCode = stagedSessionErrorCode;
+ mStagedSessionErrorMessage =
+ stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
if (sealed) {
synchronized (mLock) {
try {
@@ -499,7 +505,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.isSessionApplied = mStagedSessionApplied;
info.isSessionReady = mStagedSessionReady;
info.isSessionFailed = mStagedSessionFailed;
- info.setStagedSessionErrorCode(mStagedSessionErrorCode);
+ info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
}
return info;
}
@@ -1971,17 +1977,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStagedSessionApplied = false;
mStagedSessionFailed = false;
mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+ mStagedSessionErrorMessage = "";
}
mCallback.onStagedSessionChanged(this);
}
/** {@hide} */
- void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) {
+ void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
+ String errorMessage) {
synchronized (mLock) {
mStagedSessionReady = false;
mStagedSessionApplied = false;
mStagedSessionFailed = true;
mStagedSessionErrorCode = errorCode;
+ mStagedSessionErrorMessage = errorMessage;
+ Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
}
mCallback.onStagedSessionChanged(this);
}
@@ -1993,6 +2003,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStagedSessionApplied = true;
mStagedSessionFailed = false;
mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+ mStagedSessionErrorMessage = "";
}
mCallback.onStagedSessionChanged(this);
}
@@ -2017,6 +2028,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return mStagedSessionErrorCode;
}
+ /** {@hide} */
+ String getStagedSessionErrorMessage() {
+ return mStagedSessionErrorMessage;
+ }
+
private void destroyInternal() {
synchronized (mLock) {
mSealed = true;
@@ -2133,6 +2149,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
+ writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE,
+ mStagedSessionErrorMessage);
// TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
// we've read all sessions.
writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2253,6 +2271,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE,
SessionInfo.NO_ERROR);
+ final String stagedSessionErrorMessage = readStringAttribute(in,
+ ATTR_STAGED_SESSION_ERROR_MESSAGE);
if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
throw new IllegalArgumentException("Can't restore staged session with invalid state.");
@@ -2296,7 +2316,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
installerThread, stagingManager, sessionId, userId, installerPackageName,
installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- stagedSessionErrorCode);
+ stagedSessionErrorCode, stagedSessionErrorMessage);
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9100f6aec6d9..6eff8155dffa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -449,8 +449,7 @@ public class PackageManagerService extends IPackageManager.Stub
private static final long BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
- private static final boolean PRECOMPILED_LAYOUT_ENABLED =
- SystemProperties.getBoolean("view.precompiled_layout_enabled", false);
+ private static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts";
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
@@ -9119,7 +9118,7 @@ public class PackageManagerService extends IPackageManager.Stub
pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
}
- if (PRECOMPILED_LAYOUT_ENABLED) {
+ if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
mArtManagerService.compileLayouts(pkg);
}
@@ -16211,7 +16210,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (performDexopt) {
// Compile the layout resources.
- if (PRECOMPILED_LAYOUT_ENABLED) {
+ if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
mViewCompiler.compileLayouts(pkg);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 35626304be1a..6f1eeeb7de7a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -39,6 +39,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
@@ -258,6 +259,8 @@ class PackageManagerShellCommand extends ShellCommand {
return runSetHarmfulAppWarning();
case "get-harmful-app-warning":
return runGetHarmfulAppWarning();
+ case "get-stagedsessions":
+ return getStagedSessions();
case "uninstall-system-updates":
return uninstallSystemUpdates();
default: {
@@ -282,6 +285,28 @@ class PackageManagerShellCommand extends ShellCommand {
return -1;
}
+ private int getStagedSessions() {
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ List<SessionInfo> stagedSessionsList =
+ mInterface.getPackageInstaller().getStagedSessions().getList();
+ for (SessionInfo session: stagedSessionsList) {
+ pw.println("appPackageName = " + session.getAppPackageName()
+ + "; sessionId = " + session.getSessionId()
+ + "; isStaged = " + session.isStaged()
+ + "; isSessionReady = " + session.isSessionReady()
+ + "; isSessionApplied = " + session.isSessionApplied()
+ + "; isSessionFailed = " + session.isSessionFailed() + ";");
+ }
+ } catch (RemoteException e) {
+ pw.println("Failure ["
+ + e.getClass().getName() + " - "
+ + e.getMessage() + "]");
+ return 0;
+ }
+ return 1;
+ }
+
private int uninstallSystemUpdates() {
final PrintWriter pw = getOutPrintWriter();
List<String> failedUninstalls = new LinkedList<>();
@@ -2307,7 +2332,7 @@ class PackageManagerShellCommand extends ShellCommand {
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
break;
case "--apex":
- sessionParams.installFlags |= PackageManager.INSTALL_APEX;
+ sessionParams.setInstallAsApex();
sessionParams.setStaged();
break;
case "--multi-package":
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 563fd7f90c4b..84c8b606a9d9 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -690,6 +690,10 @@ class ShortcutPackage extends ShortcutPackageItem {
return result;
}
+ public boolean hasShareTargets() {
+ return !mShareTargets.isEmpty();
+ }
+
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fdbaba24966b..792b34c16551 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2167,6 +2167,19 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
+ @Override
+ public boolean hasShareTargets(String packageName, String packageToCheck,
+ @UserIdInt int userId) {
+ verifyCaller(packageName, userId);
+ enforceSystem();
+
+ synchronized (mLock) {
+ throwIfUserLockedL(userId);
+
+ return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets();
+ }
+ }
+
@GuardedBy("mLock")
private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
@UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 5311c2a55931..c4d27e5882c4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -153,6 +153,19 @@ public class StagingManager {
return success;
}
+ private static boolean sendMarkStagedSessionReadyRequest(int sessionId) {
+ final IApexService apex = IApexService.Stub.asInterface(
+ ServiceManager.getService("apexservice"));
+ boolean success;
+ try {
+ success = apex.markStagedSessionReady(sessionId);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to contact apexservice", re);
+ return false;
+ }
+ return success;
+ }
+
private static boolean isApexSession(@NonNull PackageInstallerSession session) {
return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
}
@@ -166,6 +179,7 @@ public class StagingManager {
if (!session.isMultiPackage()
&& isApexSession(session)) {
success = submitSessionToApexService(session, null, apexInfoList);
+
} else if (session.isMultiPackage()) {
List<PackageInstallerSession> childSessions =
Arrays.stream(session.getChildSessionIds())
@@ -179,7 +193,13 @@ public class StagingManager {
} // else this is a staged multi-package session with no APEX files.
}
- if (success && (apexInfoList.apexInfos.length > 0)) {
+ if (!success) {
+ session.setStagedSessionFailed(
+ SessionInfo.VERIFICATION_FAILED,
+ "APEX staging failed, check logcat messages from apexd for more details.");
+ }
+
+ if (apexInfoList.apexInfos.length > 0) {
// For APEXes, we validate the signature here before we mark the session as ready,
// so we fail the session early if there is a signature mismatch. For APKs, the
// signature verification will be done by the package manager at the point at which
@@ -190,16 +210,22 @@ public class StagingManager {
for (ApexInfo apexPackage : apexInfoList.apexInfos) {
if (!validateApexSignatureLocked(apexPackage.packagePath,
apexPackage.packageName)) {
- success = false;
- break;
+ session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+ "APK-container signature verification failed for package "
+ + apexPackage.packageName + ". Signature of file "
+ + apexPackage.packagePath + " does not match the signature of "
+ + " the package already installed.");
+ // TODO(b/118865310): abort the session on apexd.
+ return;
}
}
}
- if (success) {
- session.setStagedSessionReady();
- } else {
- session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+ session.setStagedSessionReady();
+ if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
+ session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+ "APEX staging failed, check logcat messages from apexd for more "
+ + "details.");
}
}
@@ -217,13 +243,20 @@ public class StagingManager {
return;
}
if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
- session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED);
+ session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED,
+ "APEX activation failed. Check logcat messages from apexd for "
+ + "more information.");
+ }
+ if (apexSessionInfo.isVerified) {
+ // Session has been previously submitted to apexd, but didn't complete all the
+ // pre-reboot verification, perhaps because the device rebooted in the meantime.
+ // Greedily re-trigger the pre-reboot verification.
+ mBgHandler.post(() -> preRebootVerification(session));
}
if (apexSessionInfo.isActivated) {
session.setStagedSessionApplied();
// TODO(b/118865310) if multi-package proceed with the installation of APKs.
}
- // TODO(b/118865310) if (apexSessionInfo.isVerified) { /* mark this as staged in apexd */ }
// In every other case apexd will retry to apply the session at next boot.
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index aaa187468f8d..2455113d8874 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -16,6 +16,10 @@
package com.android.server.pm;
+import com.google.android.collect.Sets;
+
+import com.android.internal.util.Preconditions;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -38,10 +42,6 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.util.Preconditions;
-
-import com.google.android.collect.Sets;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0e40a00077b5..13c4d886e7b1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2569,6 +2569,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = {
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
};
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index e1a911e8ada5..1d829707f180 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -825,16 +825,16 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
// like the ANR / app crashed dialogs
return canAddInternalSystemWindow ? 11 : 10;
case TYPE_APPLICATION_OVERLAY:
- return 12;
+ return canAddInternalSystemWindow ? 13 : 12;
case TYPE_DREAM:
// used for Dreams (screensavers with TYPE_DREAM windows)
- return 13;
+ return 14;
case TYPE_INPUT_METHOD:
// on-screen keyboards and other such input method user interfaces go here.
- return 14;
+ return 15;
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input method user interfaces go here.
- return 15;
+ return 16;
case TYPE_STATUS_BAR:
return 17;
case TYPE_STATUS_BAR_PANEL:
diff --git a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
index 055c941f8b0a..7f2dedb70514 100644
--- a/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
+++ b/services/core/java/com/android/server/policy/role/LegacyRoleResolutionPolicy.java
@@ -18,6 +18,7 @@ package com.android.server.policy.role;
import android.annotation.NonNull;
import android.app.role.RoleManager;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Debug;
import android.provider.Settings;
@@ -90,6 +91,17 @@ public class LegacyRoleResolutionPolicy implements RoleManagerService.RoleHolder
return CollectionUtils.singletonOrEmpty(result);
}
+ case RoleManager.ROLE_ASSISTANT: {
+ String legacyAssistant = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId);
+
+ if (legacyAssistant == null || legacyAssistant.isEmpty()) {
+ return Collections.emptyList();
+ } else {
+ return Collections.singletonList(
+ ComponentName.unflattenFromString(legacyAssistant).getPackageName());
+ }
+ }
default: {
Slog.e(LOG_TAG, "Don't know how to find legacy role holders for " + roleName);
return Collections.emptyList();
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
new file mode 100644
index 000000000000..a2c8dace9510
--- /dev/null
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -0,0 +1,235 @@
+/*
+ * 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.power;
+
+import android.attention.AttentionManagerInternal;
+import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for checking if the user is currently paying attention to the phone and
+ * notifying {@link PowerManagerService} that user activity should be renewed.
+ *
+ * This class also implements a limit of how long the extension should be, to avoid security
+ * issues where the device would never be locked.
+ */
+public class AttentionDetector {
+
+ private static final String TAG = "AttentionDetector";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Invoked whenever user attention is detected.
+ */
+ private final Runnable mOnUserAttention;
+
+ /**
+ * The maximum time, in millis, that the phone can stay unlocked because of attention events,
+ * triggered by any user.
+ */
+ @VisibleForTesting
+ protected long mMaximumExtensionMillis;
+
+ private final Object mLock;
+
+ /**
+ * {@link android.service.attention.AttentionService} API timeout.
+ */
+ private long mMaxAttentionApiTimeoutMillis;
+
+ /**
+ * Last known user activity.
+ */
+ private long mLastUserActivityTime;
+
+ @VisibleForTesting
+ protected AttentionManagerInternal mAttentionManager;
+
+ /**
+ * If we're currently waiting for an attention callback
+ */
+ private boolean mRequested;
+
+ /**
+ * Current wakefulness of the device. {@see PowerManagerInternal}
+ */
+ private int mWakefulness;
+
+ @VisibleForTesting
+ final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() {
+
+ @Override
+ public void onSuccess(int requestCode, int result, long timestamp) {
+ Slog.v(TAG, "onSuccess: " + requestCode + ", " + result
+ + " - current requestCode: " + getRequestCode());
+ synchronized (mLock) {
+ if (requestCode == getRequestCode() && mRequested) {
+ mRequested = false;
+ if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ if (DEBUG) Slog.d(TAG, "Device slept before receiving callback.");
+ return;
+ }
+ if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) {
+ mOnUserAttention.run();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(int requestCode, int error) {
+ Slog.i(TAG, "Failed to check attention: " + error);
+ synchronized (mLock) {
+ if (requestCode == getRequestCode()) {
+ mRequested = false;
+ }
+ }
+ }
+ };
+
+ public AttentionDetector(Runnable onUserAttention, Object lock) {
+ mOnUserAttention = onUserAttention;
+ mLock = lock;
+ }
+
+ public void systemReady(Context context) {
+ mAttentionManager = LocalServices.getService(AttentionManagerInternal.class);
+ mMaximumExtensionMillis = context.getResources().getInteger(
+ com.android.internal.R.integer.config_attentionMaximumExtension);
+ mMaxAttentionApiTimeoutMillis = context.getResources().getInteger(
+ com.android.internal.R.integer.config_attentionApiTimeout);
+ }
+
+ public long updateUserActivity(long nextScreenDimming) {
+ if (!isAttentionServiceSupported()) {
+ return nextScreenDimming;
+ }
+
+ final long now = SystemClock.uptimeMillis();
+ final long whenToCheck = nextScreenDimming - getAttentionTimeout();
+ final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis;
+ if (now < whenToCheck) {
+ if (DEBUG) {
+ Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now));
+ }
+ return nextScreenDimming;
+ } else if (whenToStopExtending < whenToCheck) {
+ if (DEBUG) {
+ Slog.d(TAG, "Let device sleep to avoid false results and improve security "
+ + (whenToCheck - whenToStopExtending));
+ }
+ return nextScreenDimming;
+ } else if (mRequested) {
+ if (DEBUG) {
+ Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode());
+ }
+ return whenToCheck;
+ }
+
+ // Ideally we should attribute mRequested to the result of #checkAttention, but the
+ // callback might arrive before #checkAttention returns (if there are cached results.)
+ // This means that we must assume that the request was successful, and then cancel it
+ // afterwards if AttentionManager couldn't deliver it.
+ mRequested = true;
+ final boolean sent = mAttentionManager.checkAttention(getRequestCode(),
+ getAttentionTimeout(), mCallback);
+ if (!sent) {
+ mRequested = false;
+ }
+
+ Slog.v(TAG, "Checking user attention with request code: " + getRequestCode());
+ return whenToCheck;
+ }
+
+ /**
+ * Handles user activity by cancelling any pending attention requests and keeping track of when
+ * the activity happened.
+ *
+ * @param eventTime Activity time, in uptime millis.
+ * @param event Activity type as defined in {@link PowerManager}.
+ * @return 0 when activity was ignored, 1 when handled, -1 when invalid.
+ */
+ public int onUserActivity(long eventTime, int event) {
+ switch (event) {
+ case PowerManager.USER_ACTIVITY_EVENT_ATTENTION:
+ return 0;
+ case PowerManager.USER_ACTIVITY_EVENT_OTHER:
+ case PowerManager.USER_ACTIVITY_EVENT_BUTTON:
+ case PowerManager.USER_ACTIVITY_EVENT_TOUCH:
+ case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY:
+ cancelCurrentRequestIfAny();
+ mLastUserActivityTime = eventTime;
+ return 1;
+ default:
+ if (DEBUG) {
+ Slog.d(TAG, "Attention not reset. Unknown activity event: " + event);
+ }
+ return -1;
+ }
+ }
+
+ public void onWakefulnessChangeStarted(int wakefulness) {
+ mWakefulness = wakefulness;
+ if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ cancelCurrentRequestIfAny();
+ }
+ }
+
+ private void cancelCurrentRequestIfAny() {
+ if (mRequested) {
+ mAttentionManager.cancelAttentionCheck(getRequestCode());
+ mRequested = false;
+ }
+ }
+
+ @VisibleForTesting
+ int getRequestCode() {
+ return (int) (mLastUserActivityTime % Integer.MAX_VALUE);
+ }
+
+ @VisibleForTesting
+ long getAttentionTimeout() {
+ return mMaxAttentionApiTimeoutMillis;
+ }
+
+ /**
+ * {@see AttentionManagerInternal#isAttentionServiceSupported}
+ */
+ @VisibleForTesting
+ boolean isAttentionServiceSupported() {
+ return mAttentionManager.isAttentionServiceSupported();
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.print("AttentionDetector:");
+ pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis);
+ pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis);
+ pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime);
+ pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported());
+ pw.print(" mRequested=" + mRequested);
+ }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a02787308246..3be64802c9fc 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -229,6 +229,7 @@ public final class PowerManagerService extends SystemService
private final BatterySaverController mBatterySaverController;
private final BatterySaverStateMachine mBatterySaverStateMachine;
private final BatterySavingStats mBatterySavingStats;
+ private final AttentionDetector mAttentionDetector;
private final BinderService mBinderService;
private final LocalService mLocalService;
private final NativeWrapper mNativeWrapper;
@@ -736,6 +737,7 @@ public final class PowerManagerService extends SystemService
mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
mConstants = new Constants(mHandler);
mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+ mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mBatterySavingStats = new BatterySavingStats(mLock);
mBatterySaverPolicy =
@@ -804,6 +806,7 @@ public final class PowerManagerService extends SystemService
mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
mPolicy = getLocalService(WindowManagerPolicy.class);
mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+ mAttentionDetector.systemReady(mContext);
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
@@ -1326,6 +1329,16 @@ public final class PowerManagerService extends SystemService
}
}
+ private void onUserAttention() {
+ synchronized (mLock) {
+ if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */,
+ Process.SYSTEM_UID)) {
+ updatePowerStateLocked();
+ }
+ }
+ }
+
private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime
@@ -1346,6 +1359,7 @@ public final class PowerManagerService extends SystemService
}
mNotifier.onUserActivity(event, uid);
+ mAttentionDetector.onUserActivity(eventTime, event);
if (mUserInactiveOverrideFromWindowManager) {
mUserInactiveOverrideFromWindowManager = false;
@@ -1593,6 +1607,7 @@ public final class PowerManagerService extends SystemService
if (mNotifier != null) {
mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
}
+ mAttentionDetector.onWakefulnessChangeStarted(wakefulness);
}
}
@@ -2085,6 +2100,10 @@ public final class PowerManagerService extends SystemService
nextTimeout = -1;
}
+ if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) {
+ nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout);
+ }
+
if (nextProfileTimeout > 0) {
nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
}
@@ -3477,6 +3496,7 @@ public final class PowerManagerService extends SystemService
mBatterySaverPolicy.dump(pw);
mBatterySaverStateMachine.dump(pw);
+ mAttentionDetector.dump(pw);
pw.println();
final int numProfiles = mProfilePowerState.size();
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index c0517fdc99d0..1c7596b80fd7 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -198,6 +198,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
// Make sure to implement LegacyRoleResolutionPolicy#getRoleHolders
// for a given role before adding a migration statement for it here
migrateRoleIfNecessary(RoleManager.ROLE_SMS, userId);
+ migrateRoleIfNecessary(RoleManager.ROLE_ASSISTANT, userId);
// Some vital packages state has changed since last role grant
// Run grants again
diff --git a/services/core/java/com/android/server/rollback/LocalIntentReceiver.java b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java
new file mode 100644
index 000000000000..504a3496147c
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/LocalIntentReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/** {@code IntentSender} implementation for RollbackManager internal use. */
+class LocalIntentReceiver {
+ final Consumer<Intent> mConsumer;
+
+ LocalIntentReceiver(Consumer<Intent> consumer) {
+ mConsumer = consumer;
+ }
+
+ private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+ mConsumer.accept(intent);
+ }
+ };
+
+ public IntentSender getIntentSender() {
+ return new IntentSender((IIntentSender) mLocalSender);
+ }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index 4015c4f7b391..a4f306489c60 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -49,6 +49,14 @@ class RollbackData {
*/
public Instant timestamp;
+ /**
+ * Whether this Rollback is currently in progress. This field is true from the point
+ * we commit a {@code PackageInstaller} session containing these packages to the point the
+ * {@code PackageInstaller} calls into the {@code onFinished} callback.
+ */
+ // NOTE: All accesses to this field are from the RollbackManager handler thread.
+ public boolean inProgress = false;
+
RollbackData(int rollbackId, File backupDir) {
this.rollbackId = rollbackId;
this.backupDir = backupDir;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 7f515bf63bc1..6487bd7af7ee 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -19,8 +19,6 @@ package com.android.server.rollback;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -30,17 +28,14 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
import android.content.pm.VersionedPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.storage.StorageManager;
@@ -60,13 +55,10 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
-import java.util.Set;
-import java.util.function.Consumer;
/**
* Implementation of service that manages APK level rollbacks.
@@ -116,6 +108,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
private final Context mContext;
private final HandlerThread mHandlerThread;
private final Installer mInstaller;
+ private final RollbackPackageHealthObserver mPackageHealthObserver;
RollbackManagerServiceImpl(Context context) {
mContext = context;
@@ -128,6 +121,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback"));
+ mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
+
// Kick off loading of the rollback data from strorage in a background
// thread.
// TODO: Consider loading the rollback data directly here instead, to
@@ -202,48 +197,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
@Override
- public RollbackInfo getAvailableRollback(String packageName) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_ROLLBACKS,
- "getAvailableRollback");
-
- RollbackData data = getRollbackForPackage(packageName);
- if (data == null) {
- return null;
- }
-
- // Note: The rollback for the package ought to be for the currently
- // installed version, otherwise the rollback data is out of date. In
- // that rare case, we'll check when we execute the rollback whether
- // it's out of date or not, so no need to check package versions here.
-
- for (PackageRollbackInfo info : data.packages) {
- if (info.getPackageName().equals(packageName)) {
- // TODO: Once the RollbackInfo API supports info about
- // dependant packages, add that info here.
- return new RollbackInfo(data.rollbackId, info);
- }
- }
- return null;
- }
-
- @Override
- public StringParceledListSlice getPackagesWithAvailableRollbacks() {
+ public ParceledListSlice getAvailableRollbacks() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ROLLBACKS,
- "getPackagesWithAvailableRollbacks");
+ "getAvailableRollbacks");
- final Set<String> packageNames = new HashSet<>();
synchronized (mLock) {
ensureRollbackDataLoadedLocked();
+ List<RollbackInfo> rollbacks = new ArrayList<>();
for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
RollbackData data = mAvailableRollbacks.get(i);
- for (PackageRollbackInfo info : data.packages) {
- packageNames.add(info.getPackageName());
- }
+ rollbacks.add(new RollbackInfo(data.rollbackId, data.packages));
}
+ return new ParceledListSlice<>(rollbacks);
}
- return new StringParceledListSlice(new ArrayList<>(packageNames));
}
@Override
@@ -281,18 +248,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
*/
private void executeRollbackInternal(RollbackInfo rollback,
String callerPackageName, IntentSender statusReceiver) {
- String targetPackageName = rollback.targetPackage.getPackageName();
- Log.i(TAG, "Initiating rollback of " + targetPackageName);
+ Log.i(TAG, "Initiating rollback");
- // Get the latest RollbackData for the target package.
- RollbackData data = getRollbackForPackage(targetPackageName);
+ RollbackData data = getRollbackForId(rollback.getRollbackId());
if (data == null) {
- sendFailure(statusReceiver, "No rollback available for package.");
+ sendFailure(statusReceiver, "Rollback unavailable");
return;
}
- if (data.rollbackId != rollback.getRollbackId()) {
- sendFailure(statusReceiver, "Rollback for package is out of date");
+ if (data.inProgress) {
+ sendFailure(statusReceiver, "Rollback for package is already in progress.");
return;
}
@@ -332,14 +297,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
PackageManager pm = context.getPackageManager();
try {
PackageInstaller packageInstaller = pm.getPackageInstaller();
- String installerPackageName = pm.getInstallerPackageName(targetPackageName);
- if (installerPackageName == null) {
- sendFailure(statusReceiver, "Cannot find installer package");
- return;
- }
PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- parentParams.setInstallerPackageName(installerPackageName);
parentParams.setAllowDowngrade(true);
parentParams.setMultiPackage();
int parentSessionId = packageInstaller.createSession(parentParams);
@@ -348,6 +307,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
for (PackageRollbackInfo info : data.packages) {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
+ if (installerPackageName == null) {
+ sendFailure(statusReceiver, "Cannot find installer package");
+ return;
+ }
params.setInstallerPackageName(installerPackageName);
params.setAllowDowngrade(true);
int sessionId = packageInstaller.createSession(params);
@@ -371,30 +335,39 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
final LocalIntentReceiver receiver = new LocalIntentReceiver(
(Intent result) -> {
- int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status != PackageInstaller.STATUS_SUCCESS) {
- sendFailure(statusReceiver, "Rollback downgrade install failed: "
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
- return;
- }
-
- addRecentlyExecutedRollback(rollback);
- sendSuccess(statusReceiver);
-
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
-
- // TODO: This call emits the warning "Calling a method in the
- // system process without a qualified user". Fix that.
- // TODO: Limit this to receivers holding the
- // MANAGE_ROLLBACKS permission?
- mContext.sendBroadcast(broadcast);
+ getHandler().post(() -> {
+ // We've now completed the rollback, so we mark it as no longer in
+ // progress.
+ data.inProgress = false;
+
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status != PackageInstaller.STATUS_SUCCESS) {
+ sendFailure(statusReceiver,
+ "Rollback downgrade install failed: "
+ + result.getStringExtra(
+ PackageInstaller.EXTRA_STATUS_MESSAGE));
+ return;
+ }
+
+ addRecentlyExecutedRollback(rollback);
+ sendSuccess(statusReceiver);
+
+ Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
+
+ // TODO: This call emits the warning "Calling a method in the
+ // system process without a qualified user". Fix that.
+ // TODO: Limit this to receivers holding the
+ // MANAGE_ROLLBACKS permission?
+ mContext.sendBroadcast(broadcast);
+ });
}
);
+ data.inProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
- Log.e(TAG, "Unable to roll back " + targetPackageName, e);
+ Log.e(TAG, "Rollback failed", e);
sendFailure(statusReceiver, "IOException: " + e.toString());
return;
}
@@ -525,9 +498,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
boolean changed = false;
while (iter.hasNext()) {
RollbackInfo rollback = iter.next();
- if (packageName.equals(rollback.targetPackage.getPackageName())) {
- iter.remove();
- changed = true;
+ for (PackageRollbackInfo info : rollback.getPackages()) {
+ if (packageName.equals(info.getPackageName())) {
+ iter.remove();
+ changed = true;
+ break;
+ }
}
}
@@ -774,10 +750,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
getHandler().post(() -> {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- // TODO(narayan): Should we make sure we're in the middle of a session commit for a
- // a package with this package name ? Otherwise it's possible we may roll back data
- // for some other downgrade.
- if (getRollbackForPackage(packageName) == null) {
+ final RollbackData rollbackData = getRollbackForPackage(packageName);
+ if (rollbackData == null) {
+ pmi.finishPackageInstall(token, false);
+ return;
+ }
+
+ if (!rollbackData.inProgress) {
+ Log.e(TAG, "Request to restore userData for: " + packageName
+ + ", but no rollback in progress.");
pmi.finishPackageInstall(token, false);
return;
}
@@ -805,26 +786,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
});
}
- private class LocalIntentReceiver {
- final Consumer<Intent> mConsumer;
-
- LocalIntentReceiver(Consumer<Intent> consumer) {
- mConsumer = consumer;
- }
-
- private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
- IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- getHandler().post(() -> mConsumer.accept(intent));
- }
- };
-
- public IntentSender getIntentSender() {
- return new IntentSender((IIntentSender) mLocalSender);
- }
- }
-
/**
* Gets the version of the package currently installed.
* Returns null if the package is not currently installed.
@@ -891,7 +852,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
ensureRollbackDataLoadedLocked();
mAvailableRollbacks.add(data);
}
-
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and commiting any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
+ List<String> packages = new ArrayList<>();
+ for (int i = 0; i < data.packages.size(); i++) {
+ packages.add(data.packages.get(i).getPackageName());
+ }
+ mPackageHealthObserver.startObservingHealth(packages,
+ ROLLBACK_LIFETIME_DURATION_MILLIS);
scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS);
} catch (IOException e) {
Log.e(TAG, "Unable to enable rollback", e);
@@ -928,6 +899,25 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return null;
}
+ /*
+ * Returns the RollbackData, if any, for an available rollback with the
+ * given rollbackId.
+ */
+ private RollbackData getRollbackForId(int rollbackId) {
+ synchronized (mLock) {
+ // TODO: Have ensureRollbackDataLoadedLocked return the list of
+ // available rollbacks, to hopefully avoid forgetting to call it?
+ ensureRollbackDataLoadedLocked();
+ for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
+ RollbackData data = mAvailableRollbacks.get(i);
+ if (data.rollbackId == rollbackId) {
+ return data;
+ }
+ }
+ }
+ return null;
+ }
+
@GuardedBy("mLock")
private int allocateRollbackIdLocked() throws IOException {
int n = 0;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
new file mode 100644
index 000000000000..3954a1178e09
--- /dev/null
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -0,0 +1,98 @@
+/*
+ * 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.rollback;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+
+import java.util.List;
+
+/**
+ * {@code PackageHealthObserver} for {@code RollbackManagerService}.
+ *
+ * @hide
+ */
+public final class RollbackPackageHealthObserver implements PackageHealthObserver {
+ private static final String TAG = "RollbackPackageHealthObserver";
+ private static final String NAME = "rollback-observer";
+ private Context mContext;
+ private Handler mHandler;
+
+ RollbackPackageHealthObserver(Context context) {
+ mContext = context;
+ HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
+ handlerThread.start();
+ mHandler = handlerThread.getThreadHandler();
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ }
+
+ @Override
+ public boolean onHealthCheckFailed(String packageName) {
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageName.equals(packageRollback.getPackageName())) {
+ // TODO(zezeozue): Only rollback if rollback version == failed package version
+ mHandler.post(() -> executeRollback(rollbackManager, rollback));
+ return true;
+ }
+ }
+ }
+ // Don't handle the notification, no rollbacks available
+ return false;
+ }
+
+ /**
+ * Start observing health of {@code packages} for {@code durationMs}.
+ * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
+ */
+ public void startObservingHealth(List<String> packages, long durationMs) {
+ PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+ }
+
+ private void executeRollback(RollbackManager manager, RollbackInfo rollback) {
+ // TODO(zezeozue): Log initiated metrics
+ LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
+ mHandler.post(() -> {
+ int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ // TODO(zezeozue); Log success metrics
+ // Rolledback successfully, no action required by other observers
+ } else {
+ // TODO(zezeozue); Log failure metrics
+ // Rollback failed other observers should have a shot
+ }
+ });
+ });
+ manager.commitRollback(rollback, rollbackReceiver.getIntentSender());
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 7738be9d32bb..3b24b3e9a444 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -114,13 +114,9 @@ class RollbackStore {
for (int i = 0; i < array.length(); ++i) {
JSONObject element = array.getJSONObject(i);
int rollbackId = element.getInt("rollbackId");
- String packageName = element.getString("packageName");
- long higherVersionCode = element.getLong("higherVersionCode");
- long lowerVersionCode = element.getLong("lowerVersionCode");
- PackageRollbackInfo target = new PackageRollbackInfo(
- new VersionedPackage(packageName, higherVersionCode),
- new VersionedPackage(packageName, lowerVersionCode));
- RollbackInfo rollback = new RollbackInfo(rollbackId, target);
+ List<PackageRollbackInfo> packages = packageRollbackInfosFromJson(
+ element.getJSONArray("packages"));
+ RollbackInfo rollback = new RollbackInfo(rollbackId, packages);
recentlyExecutedRollbacks.add(rollback);
}
} catch (IOException | JSONException e) {
@@ -155,18 +151,8 @@ class RollbackStore {
void saveAvailableRollback(RollbackData data) throws IOException {
try {
JSONObject dataJson = new JSONObject();
- JSONArray packagesJson = new JSONArray();
- for (PackageRollbackInfo info : data.packages) {
- JSONObject infoJson = new JSONObject();
- infoJson.put("packageName", info.getPackageName());
- infoJson.put("higherVersionCode",
- info.getVersionRolledBackFrom().getLongVersionCode());
- infoJson.put("lowerVersionCode",
- info.getVersionRolledBackTo().getVersionCode());
- packagesJson.put(infoJson);
- }
dataJson.put("rollbackId", data.rollbackId);
- dataJson.put("packages", packagesJson);
+ dataJson.put("packages", toJson(data.packages));
dataJson.put("timestamp", data.timestamp.toString());
PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
@@ -200,11 +186,7 @@ class RollbackStore {
RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
JSONObject element = new JSONObject();
element.put("rollbackId", rollback.getRollbackId());
- element.put("packageName", rollback.targetPackage.getPackageName());
- element.put("higherVersionCode",
- rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
- element.put("lowerVersionCode",
- rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
+ element.put("packages", toJson(rollback.getPackages()));
array.put(element);
}
@@ -229,18 +211,7 @@ class RollbackStore {
int rollbackId = dataJson.getInt("rollbackId");
RollbackData data = new RollbackData(rollbackId, backupDir);
-
- JSONArray packagesJson = dataJson.getJSONArray("packages");
- for (int i = 0; i < packagesJson.length(); ++i) {
- JSONObject infoJson = packagesJson.getJSONObject(i);
- String packageName = infoJson.getString("packageName");
- long higherVersionCode = infoJson.getLong("higherVersionCode");
- long lowerVersionCode = infoJson.getLong("lowerVersionCode");
- data.packages.add(new PackageRollbackInfo(
- new VersionedPackage(packageName, higherVersionCode),
- new VersionedPackage(packageName, lowerVersionCode)));
- }
-
+ data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages")));
data.timestamp = Instant.parse(dataJson.getString("timestamp"));
return data;
} catch (JSONException | DateTimeParseException e) {
@@ -248,6 +219,40 @@ class RollbackStore {
}
}
+ private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
+ JSONObject json = new JSONObject();
+ json.put("packageName", info.getPackageName());
+ json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode());
+ json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode());
+ return json;
+ }
+
+ private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
+ String packageName = json.getString("packageName");
+ long higherVersionCode = json.getLong("higherVersionCode");
+ long lowerVersionCode = json.getLong("lowerVersionCode");
+ return new PackageRollbackInfo(
+ new VersionedPackage(packageName, higherVersionCode),
+ new VersionedPackage(packageName, lowerVersionCode));
+ }
+
+ private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException {
+ JSONArray json = new JSONArray();
+ for (PackageRollbackInfo info : infos) {
+ json.put(toJson(info));
+ }
+ return json;
+ }
+
+ private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json)
+ throws JSONException {
+ List<PackageRollbackInfo> infos = new ArrayList<>();
+ for (int i = 0; i < json.length(); ++i) {
+ infos.add(packageRollbackInfoFromJson(json.getJSONObject(i)));
+ }
+ return infos;
+ }
+
/**
* Deletes a file completely.
* If the file is a directory, its contents are deleted as well.
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
index 56db32a3071d..146c51688531 100644
--- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -41,8 +41,8 @@ public class SignatureVerifier {
private static final boolean DBG = false;
private static final String DEBUG_KEY =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
- + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1"
+ + "x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==";
private static final String PROD_KEY =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp"
+ "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g==";
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index acede7d4fa90..c6d2870a24c9 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -246,6 +246,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
@Nullable
private final KernelCpuThreadReader mKernelCpuThreadReader;
+ private long mDebugElapsedClockPreviousValue = 0;
+ private long mDebugElapsedClockPullCount = 0;
+ private long mDebugFailingElapsedClockPreviousValue = 0;
+ private long mDebugFailingElapsedClockPullCount = 0;
private BatteryStatsHelper mBatteryStatsHelper = null;
private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
@@ -1726,6 +1730,56 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
+ private void pullDebugElapsedClock(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ final long elapsedMillis = SystemClock.elapsedRealtime();
+ final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0
+ ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue;
+
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeLong(mDebugElapsedClockPullCount);
+ e.writeLong(elapsedMillis);
+ // Log it twice to be able to test multi-value aggregation from ValueMetric.
+ e.writeLong(elapsedMillis);
+ e.writeLong(clockDiffMillis);
+ e.writeInt(1 /* always set */);
+ pulledData.add(e);
+
+ if (mDebugElapsedClockPullCount % 2 == 1) {
+ StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e2.writeLong(mDebugElapsedClockPullCount);
+ e2.writeLong(elapsedMillis);
+ // Log it twice to be able to test multi-value aggregation from ValueMetric.
+ e2.writeLong(elapsedMillis);
+ e2.writeLong(clockDiffMillis);
+ e2.writeInt(2 /* set on odd pulls */);
+ pulledData.add(e2);
+ }
+
+ mDebugElapsedClockPullCount++;
+ mDebugElapsedClockPreviousValue = elapsedMillis;
+ }
+
+ private void pullDebugFailingElapsedClock(int tagId,
+ long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ final long elapsedMillis = SystemClock.elapsedRealtime();
+ // Fails every 10 buckets.
+ if (mDebugFailingElapsedClockPullCount++ % 10 == 0) {
+ mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+ throw new RuntimeException("Failing debug elapsed clock");
+ }
+
+ e.writeLong(mDebugFailingElapsedClockPullCount);
+ e.writeLong(elapsedMillis);
+ // Log it twice to be able to test multi-value aggregation from ValueMetric.
+ e.writeLong(elapsedMillis);
+ e.writeLong(mDebugFailingElapsedClockPreviousValue == 0
+ ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue);
+ mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+ pulledData.add(e);
+ }
+
/**
* Pulls various data.
*/
@@ -1843,7 +1897,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
- case StatsLog.NUM_FINGERPRINTS: {
+ case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
pullNumFingerprints(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
@@ -1892,6 +1946,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.DEBUG_ELAPSED_CLOCK: {
+ pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
+ case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
+ pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index 23c042a57ac8..4adce586e9a5 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -66,6 +66,7 @@ public class TestHarnessModeService extends SystemService {
private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+ private boolean mShouldSetUpTestHarnessMode;
public TestHarnessModeService(Context context) {
super(context);
@@ -96,6 +97,7 @@ public class TestHarnessModeService extends SystemService {
// There's no data to apply, so leave it as-is.
return;
}
+ mShouldSetUpTestHarnessMode = true;
PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
@@ -124,6 +126,9 @@ public class TestHarnessModeService extends SystemService {
}
private void disableAutoSync() {
+ if (!mShouldSetUpTestHarnessMode) {
+ return;
+ }
UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
ContentResolver
.setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 69040a2ea974..b0ef8a0d4209 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -469,6 +469,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
*/
private void extractColors(WallpaperData wallpaper) {
String cropFile = null;
+ boolean defaultImageWallpaper = false;
int wallpaperId;
if (wallpaper.equals(mFallbackWallpaper)) {
@@ -482,6 +483,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|| wallpaper.wallpaperComponent == null;
if (imageWallpaper && wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
cropFile = wallpaper.cropFile.getAbsolutePath();
+ } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
+ defaultImageWallpaper = true;
}
wallpaperId = wallpaper.wallpaperId;
}
@@ -493,6 +496,25 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
colors = WallpaperColors.fromBitmap(bitmap);
bitmap.recycle();
}
+ } else if (defaultImageWallpaper) {
+ // There is no crop and source file because this is default image wallpaper.
+ try (final InputStream is =
+ WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
+ if (is != null) {
+ try {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+ if (bitmap != null) {
+ colors = WallpaperColors.fromBitmap(bitmap);
+ bitmap.recycle();
+ }
+ } catch (OutOfMemoryError e) {
+ Slog.w(TAG, "Can't decode default wallpaper stream", e);
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Can't close default wallpaper stream", e);
+ }
}
if (colors == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 65d66f44b5dd..e817dd47e756 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,22 +558,26 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
}
/**
- * Pause all activities in either all of the stacks or just the back stacks.
+ * Pause all activities in either all of the stacks or just the back stacks. This is done before
+ * resuming a new activity and to make sure that previously active activities are
+ * paused in stacks that are no longer visible or in pinned windowing mode. This does not
+ * pause activities in visible stacks, so if an activity is launched within the same stack/task,
+ * then we should explicitly pause that stack's top activity.
* @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
* @param resuming The resuming activity.
* @param dontWait The resuming activity isn't going to wait for all activities to be paused
* before resuming.
- * @return true if any activity was paused as a result of this call.
+ * @return {@code true} if any activity was paused as a result of this call.
*/
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
boolean someActivityPaused = false;
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = mStacks.get(stackNdx);
- // TODO(b/111541062): Check if resumed activity on this display instead
- if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
- && stack.getResumedActivity() != null) {
+ final ActivityRecord resumedActivity = stack.getResumedActivity();
+ if (resumedActivity != null
+ && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
- " mResumedActivity=" + stack.getResumedActivity());
+ " mResumedActivity=" + resumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
dontWait);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8634d88319a..b7c35c083174 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,30 +1946,90 @@ final class ActivityRecord extends ConfigurationContainer {
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
WindowVisibilityItem.obtain(true /* showWindow */));
- if (shouldPauseWhenBecomingVisible()) {
- // An activity must be in the {@link PAUSING} state for the system to validate
- // the move to {@link PAUSED}.
- setState(PAUSING, "makeVisibleIfNeeded");
+ makeActiveIfNeeded(null /* activeActivity*/);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
+ }
+ }
+
+ /**
+ * Make activity resumed or paused if needed.
+ * @param activeActivity an activity that is resumed or just completed pause action.
+ * We won't change the state of this activity.
+ */
+ boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
+ if (shouldResumeActivity(activeActivity)) {
+ if (DEBUG_VISIBILITY) {
+ Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
+ }
+ return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
+ null /* options */);
+ } else if (shouldPauseActivity(activeActivity)) {
+ if (DEBUG_VISIBILITY) {
+ Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
+ }
+ // An activity must be in the {@link PAUSING} state for the system to validate
+ // the move to {@link PAUSED}.
+ setState(PAUSING, "makeVisibleIfNeeded");
+ try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
PauseActivityItem.obtain(finishing, false /* userLeaving */,
configChangeFlags, false /* dontReport */));
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
}
+ return false;
}
- /** Check if activity should be moved to PAUSED state when it becomes visible. */
- private boolean shouldPauseWhenBecomingVisible() {
- // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
+ /**
+ * Check if activity should be moved to PAUSED state. The activity:
+ * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
+ * - should be non-focusable
+ * - should not be currently pausing or paused
+ * @param activeActivity the activity that is active or just completed pause action. We won't
+ * resume if this activity is active.
+ */
+ private boolean shouldPauseActivity(ActivityRecord activeActivity) {
+ return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
+ }
+
+ /**
+ * Check if activity should be moved to RESUMED state. The activity:
+ * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
+ * - should be focusable
+ * @param activeActivity the activity that is active or just completed pause action. We won't
+ * resume if this activity is active.
+ */
+ private boolean shouldResumeActivity(ActivityRecord activeActivity) {
+ return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
+ }
+
+ /**
+ * Check if activity is eligible to be made active (resumed of paused). The activity:
+ * - should be paused, stopped or stopping
+ * - should not be the currently active one or launching behind other tasks
+ * - should be either the topmost in task, or right below the top activity that is finishing
+ * If all of these conditions are not met at the same time, the activity cannot be made active.
+ */
+ private boolean shouldMakeActive(ActivityRecord activeActivity) {
+ // If the activity is stopped, stopping, cycle to an active state. We avoid doing
// this when there is an activity waiting to become translucent as the extra binder
// calls will lead to noticeable jank. A later call to
- // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
- // paused state. We also avoid doing this for the activity the stack supervisor
- // considers the resumed activity, as normal means will bring the activity from STOPPED
- // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
- if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
- || isResumedActivityOnDisplay()) {
+ // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
+ // active state.
+ if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
+ || getActivityStack().mTranslucentActivityWaiting != null) {
+ return false;
+ }
+
+ if (this == activeActivity) {
+ return false;
+ }
+
+ if (this.mLaunchTaskBehind) {
+ // This activity is being launched from behind, which means that it's not intended to be
+ // presented to user right now, even if it's set to be visible.
return false;
}
@@ -1979,14 +2039,14 @@ final class ActivityRecord extends ConfigurationContainer {
throw new IllegalStateException("Activity not found in its task");
}
if (positionInTask == task.mActivities.size() - 1) {
- // It's the topmost activity in the task - should become paused now
+ // It's the topmost activity in the task - should become resumed now
return true;
}
// Check if activity above is finishing now and this one becomes the topmost in task.
final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
if (activityAbove.finishing && results == null) {
- // We will only allow pausing if activity above wasn't launched for result. Otherwise it
- // will cause this activity to resume before getting result.
+ // We will only allow making active if activity above wasn't launched for result.
+ // Otherwise it will cause this activity to resume before getting result.
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 891c3da90b93..c97e4e8a9bc0 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,6 +357,11 @@ class ActivityStack extends ConfigurationContainer {
*/
boolean mForceHidden = false;
+ /**
+ * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
+ */
+ boolean mInResumeTopActivity = false;
+
private boolean mUpdateBoundsDeferred;
private boolean mUpdateBoundsDeferredCalled;
private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1732,6 +1737,7 @@ class ActivityStack extends ConfigurationContainer {
"Activity paused: token=" + token + ", timeout=" + timeout);
final ActivityRecord r = isInStackLocked(token);
+
if (r != null) {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
@@ -2088,8 +2094,7 @@ class ActivityStack extends ConfigurationContainer {
boolean aboveTop = top != null;
final boolean stackShouldBeVisible = shouldBeVisible(starting);
boolean behindFullscreenActivity = !stackShouldBeVisible;
- boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
- && (isInStackLocked(starting) == null);
+ boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
final boolean isTopNotPinnedStack =
isAttached() && getDisplay().isTopNotPinnedStack(this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2150,6 +2155,10 @@ class ActivityStack extends ConfigurationContainer {
if (r.handleAlreadyVisible()) {
resumeNextActivity = false;
}
+
+ if (notifyClients) {
+ r.makeActiveIfNeeded(starting);
+ }
} else {
r.makeVisibleIfNeeded(starting, notifyClients);
}
@@ -2286,7 +2295,7 @@ class ActivityStack extends ConfigurationContainer {
* Check if the display to which this stack is attached has
* {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
*/
- private boolean canShowWithInsecureKeyguard() {
+ boolean canShowWithInsecureKeyguard() {
final ActivityDisplay activityDisplay = getDisplay();
if (activityDisplay == null) {
throw new IllegalStateException("Stack is not attached to any display, stackId="
@@ -2327,7 +2336,7 @@ class ActivityStack extends ConfigurationContainer {
r.setVisible(true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
+ mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
return true;
}
}
@@ -2505,7 +2514,7 @@ class ActivityStack extends ConfigurationContainer {
*/
@GuardedBy("mService")
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
- if (mStackSupervisor.inResumeTopActivity) {
+ if (mInResumeTopActivity) {
// Don't even start recursing.
return false;
}
@@ -2513,7 +2522,7 @@ class ActivityStack extends ConfigurationContainer {
boolean result = false;
try {
// Protect against recursion.
- mStackSupervisor.inResumeTopActivity = true;
+ mInResumeTopActivity = true;
result = resumeTopActivityInnerLocked(prev, options);
// When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2528,7 +2537,7 @@ class ActivityStack extends ConfigurationContainer {
checkReadyForSleep();
}
} finally {
- mStackSupervisor.inResumeTopActivity = false;
+ mInResumeTopActivity = false;
}
return result;
@@ -2561,7 +2570,7 @@ class ActivityStack extends ConfigurationContainer {
// Find the next top-most activity to resume in this stack that is not finishing and is
// focusable. If it is not focusable, we will fall into the case below to resume the
// top activity in the next focusable task.
- final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+ ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
final boolean hasRunningActivity = next != null;
@@ -2649,6 +2658,12 @@ class ActivityStack extends ConfigurationContainer {
if (!mRootActivityContainer.allPausedActivitiesComplete()) {
if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
"resumeTopActivityLocked: Skip resume: some activity pausing.");
+
+ // Adding previous activity to the waiting visible list, or it would be stopped
+ // before top activity being visible.
+ if (prev != null && !next.nowVisible) {
+ mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
+ }
return false;
}
@@ -2858,7 +2873,9 @@ class ActivityStack extends ConfigurationContainer {
// the screen based on the new activity order.
boolean notUpdated = true;
- if (isFocusedStackOnDisplay()) {
+ // Activity should also be visible if set mLaunchTaskBehind to true (see
+ // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
+ if (shouldBeVisible(next)) {
// We have special rotation behavior when here is some active activity that
// requests specific orientation or Keyguard is locked. Make sure all activity
// visibilities are set correctly as well as the transition is updated if needed
@@ -4087,6 +4104,12 @@ class ActivityStack extends ConfigurationContainer {
mStackSupervisor.mFinishingActivities.add(r);
r.resumeKeyDispatchingLocked();
mRootActivityContainer.resumeFocusedStacksTopActivities();
+ // If activity was not paused at this point - explicitly pause it to start finishing
+ // process. Finishing will be completed once it reports pause back.
+ if (r.isState(RESUMED) && mPausingActivity != null) {
+ startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
+ false /* dontWait */);
+ }
return r;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 3a288ca5560d..a83ef34f1cac 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,9 +327,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
*/
PowerManager.WakeLock mGoingToSleep;
- /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
- boolean inResumeTopActivity;
-
/**
* Temporary rect used during docked stack resize calculation so we don't need to create a new
* object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 280709461c98..43c12064a3c1 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,7 +179,10 @@ public class ActivityStartController {
.setActivityOptions(options.toBundle())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
- if (mSupervisor.inResumeTopActivity) {
+ final ActivityDisplay display =
+ mService.mRootActivityContainer.getActivityDisplay(displayId);
+ final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
+ if (homeStack != null && homeStack.mInResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
// resumed (to avoid recursive resume) and will stay that way until something pokes it
// again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d36e545aa74f..22a81efc033a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -747,12 +747,23 @@ class ActivityStarter {
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
- // not sure if we need to create START_ABORTED_BACKGROUND so for now piggybacking
- // on START_ABORTED
+ boolean abortBackgroundStart = false;
if (!abort) {
- abort |= shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage,
- realCallingUid, callerApp, originatingPendingIntent,
+ abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, callerApp, originatingPendingIntent,
allowBackgroundActivityStart, intent);
+ abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled());
+ // TODO: remove this toast after feature development is done
+ if (abortBackgroundStart) {
+ final String toastMsg = abort
+ ? "Background activity start from " + callingPackage
+ + " blocked. See go/q-bg-block."
+ : "This background activity start from " + callingPackage
+ + " will be blocked in future Q builds. See go/q-bg-block.";
+ mService.mUiHandler.post(() -> {
+ Toast.makeText(mService.mContext, toastMsg, Toast.LENGTH_LONG).show();
+ });
+ }
}
// Merge the two options bundles, while realCallerOptions takes precedence.
@@ -798,8 +809,6 @@ class ActivityStarter {
// We pretend to the caller that it was really started, but
// they will just get a cancel result.
ActivityOptions.abort(checkedOptions);
- maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
- null /*r*/, originatingPendingIntent, true /*abortedStart*/);
return START_ABORTED;
}
@@ -818,6 +827,8 @@ class ActivityStarter {
final int flags = intent.getFlags();
Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
newIntent.setFlags(flags
+ | FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -892,8 +903,11 @@ class ActivityStarter {
mService.onStartActivitySetDidAppSwitch();
mController.doPendingActivityLaunches(false);
- maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r,
- originatingPendingIntent, false /*abortedStart*/);
+ // maybe log to TRON, but only if we haven't already in shouldAbortBackgroundActivityStart()
+ if (!abortBackgroundStart) {
+ maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r,
+ originatingPendingIntent, false /*abortedStart*/);
+ }
return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
true /* doResume */, checkedOptions, inTask, outActivity);
@@ -903,11 +917,9 @@ class ActivityStarter {
final String callingPackage, int realCallingUid, WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart,
Intent intent) {
- if (mService.isBackgroundActivityStartsEnabled()) {
- return false;
- }
// don't abort for the most important UIDs
- if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) {
+ if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID
+ || callingUid == Process.NFC_UID) {
return false;
}
// don't abort if the callerApp has any visible activity
@@ -915,14 +927,15 @@ class ActivityStarter {
return false;
}
// don't abort if the callingUid is in the foreground or is a persistent system process
- final boolean isCallingUidForeground = isUidForeground(callingUid);
+ final boolean isCallingUidForeground = mService.isUidForeground(callingUid);
final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
callingUid);
if (isCallingUidForeground || isCallingUidPersistentSystemProcess) {
return false;
}
// take realCallingUid into consideration
- final boolean isRealCallingUidForeground = isUidForeground(realCallingUid);
+ final boolean isRealCallingUidForeground = mService.isUidForeground(
+ realCallingUid);
final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
realCallingUid);
if (realCallingUid != callingUid) {
@@ -949,8 +962,8 @@ class ActivityStarter {
if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
return false;
}
- // anything that has fallen through will currently be aborted
- Slog.w(TAG, "Blocking background activity start [callingPackage: " + callingPackage
+ // anything that has fallen through would currently be aborted
+ Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
+ "; isCallingUidForeground: " + isCallingUidForeground
+ "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
@@ -962,21 +975,11 @@ class ActivityStarter {
+ "; isBgStartWhitelisted: " + allowBackgroundActivityStart
+ "; intent: " + intent
+ "]");
- // TODO: remove this toast after feature development is done
- mService.mUiHandler.post(() -> {
- Toast.makeText(mService.mContext,
- "Blocking background activity start for " + callingPackage,
- Toast.LENGTH_SHORT).show();
- });
+ maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
+ null /*r*/, originatingPendingIntent, true /*abortedStart*/);
return true;
}
- /** Returns true if uid has a visible window or its process is in a top state. */
- private boolean isUidForeground(int uid) {
- return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
- || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
- }
-
/** Returns true if uid is in a persistent state. */
private boolean isUidPersistentSystemProcess(int uid) {
return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
@@ -1629,7 +1632,7 @@ class ActivityStarter {
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
- mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+ mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 67b00b2cfbf1..1a5e6a14e733 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -489,4 +489,7 @@ public abstract class ActivityTaskManagerInternal {
*/
public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId,
boolean reducedResolution);
+
+ /** Returns true if uid has a visible window or its process is in a top state. */
+ public abstract boolean isUidForeground(int uid);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ea6f4cc4ddbc..5fabde45db55 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4399,6 +4399,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ @Override
+ public void registerRemoteAnimationsForDisplay(int displayId,
+ RemoteAnimationDefinition definition) {
+ mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+ "registerRemoteAnimations");
+ definition.setCallingPid(Binder.getCallingPid());
+ synchronized (mGlobalLock) {
+ final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId);
+ if (display == null) {
+ Slog.e(TAG, "Couldn't find display with id: " + displayId);
+ return;
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ display.mDisplayContent.registerRemoteAnimations(definition);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
@Override
public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
@@ -5632,6 +5653,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return mActiveUids.get(uid, PROCESS_STATE_NONEXISTENT);
}
+ boolean isUidForeground(int uid) {
+ return (getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
+ || mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
+ }
+
/**
* @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on
* the whitelist
@@ -7041,5 +7067,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution);
}
}
+
+ @Override
+ public boolean isUidForeground(int uid) {
+ synchronized (mGlobalLock) {
+ return ActivityTaskManagerService.this.isUidForeground(uid);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f3a363a30cf8..6dc73bbb80cb 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -29,6 +29,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPE
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
@@ -1662,6 +1663,15 @@ public class AppTransition implements Dump {
"applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:"
+ " anim=" + a + " transit=" + appTransitionToString(transit)
+ " isEntrance=true" + " Callers=" + Debug.getCallers(3));
+ } else if (transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE) {
+ // In the absence of a specific adapter, we just want to keep everything stationary.
+ a = new AlphaAnimation(1.f, 1.f);
+ a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG, "applyAnimation:"
+ + " anim=" + a + " transit=" + appTransitionToString(transit)
+ + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
+ }
} else {
int animAttr = 0;
switch (transit) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 49308b8f92b4..8f0a7c08fca8 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -76,6 +76,7 @@ public class AppTransitionController {
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final WallpaperController mWallpaperControllerLocked;
+ private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
@@ -85,6 +86,10 @@ public class AppTransitionController {
mWallpaperControllerLocked = mDisplayContent.mWallpaperController;
}
+ void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+ mRemoteAnimationDefinition = definition;
+ }
+
/**
* Handle application transition for given display.
*/
@@ -216,6 +221,21 @@ public class AppTransitionController {
return mainWindow != null ? mainWindow.mAttrs : null;
}
+ RemoteAnimationAdapter getRemoteAnimationOverride(AppWindowToken animLpToken, int transit,
+ ArraySet<Integer> activityTypes) {
+ final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
+ if (definition != null) {
+ final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+ if (adapter != null) {
+ return adapter;
+ }
+ }
+ if (mRemoteAnimationDefinition == null) {
+ return null;
+ }
+ return mRemoteAnimationDefinition.getAdapter(transit, activityTypes);
+ }
+
/**
* Overrides the pending transition with the remote animation defined for the transition in the
* set of defined remote animations in the app window token.
@@ -229,11 +249,8 @@ public class AppTransitionController {
if (animLpToken == null) {
return;
}
- final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
- if (definition == null) {
- return;
- }
- final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+ final RemoteAnimationAdapter adapter =
+ getRemoteAnimationOverride(animLpToken, transit, activityTypes);
if (adapter != null) {
animLpToken.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(
adapter);
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index 29645f68b2fb..ed029cd722cf 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
+
import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT;
import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR;
import static com.android.server.wm.AppWindowThumbnailProto.WIDTH;
@@ -82,7 +85,8 @@ class AppWindowThumbnail implements Animatable {
.setName("thumbnail anim: " + appToken.toString())
.setBufferSize(mWidth, mHeight)
.setFormat(PixelFormat.TRANSLUCENT)
- .setMetadata(appToken.windowType,
+ .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType)
+ .setMetadata(METADATA_OWNER_UID,
window != null ? window.mOwnerUid : Binder.getCallingUid())
.build();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 65b36a092228..a52f1af3b8b1 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -105,6 +105,7 @@ import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.IApplicationToken;
import android.view.InputApplicationHandle;
+import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -1621,6 +1622,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
t.reparent(getSurfaceControl(), mTransitChangeLeash);
onAnimationLeashCreated(t, mTransitChangeLeash);
+ // Skip creating snapshot if this transition is controlled by a remote animator which
+ // doesn't need it.
+ ArraySet<Integer> activityTypes = new ArraySet<>();
+ activityTypes.add(getActivityType());
+ RemoteAnimationAdapter adapter =
+ mDisplayContent.mAppTransitionController.getRemoteAnimationOverride(
+ this, TRANSIT_TASK_CHANGE_WINDOWING_MODE, activityTypes);
+ if (adapter != null && !adapter.getChangeNeedsSnapshot()) {
+ return;
+ }
+
if (mThumbnail == null && getTask() != null) {
final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController;
final ArraySet<Task> tasks = new ArraySet<>();
@@ -1639,6 +1651,11 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
return mTransitChangeLeash != null || isChangeTransition(mTransit);
}
+ @VisibleForTesting
+ AppWindowThumbnail getThumbnail() {
+ return mThumbnail;
+ }
+
@Override
void checkAppWindowsReadyToShow() {
if (allDrawn == mLastAllDrawn) {
@@ -2323,11 +2340,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
}
- private int getDefaultChangeTransitionDuration() {
- return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION
- * mWmService.getTransitionAnimationScaleLocked());
- }
-
boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
@@ -2349,7 +2361,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
AnimationAdapter thumbnailAdapter = null;
getAnimationBounds(mTmpPoint, mTmpRect);
- boolean isChanging = isChangeTransition(transit) && mThumbnail != null;
+ boolean isChanging = isChangeTransition(transit) && enter;
// Delaying animation start isn't compatible with remote animations at all.
if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
@@ -2361,18 +2373,20 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
adapter = adapters.mAdapter;
thumbnailAdapter = adapters.mThumbnailAdapter;
} else if (isChanging) {
- int duration = getDefaultChangeTransitionDuration();
+ final float durationScale = mWmService.getTransitionAnimationScaleLocked();
mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
adapter = new LocalAnimationAdapter(
new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
- getDisplayContent().getDisplayInfo(), duration,
+ getDisplayContent().getDisplayInfo(), durationScale,
true /* isAppAnimation */, false /* isThumbnail */),
mWmService.mSurfaceAnimationRunner);
- thumbnailAdapter = new LocalAnimationAdapter(
- new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
- getDisplayContent().getDisplayInfo(), duration,
- true /* isAppAnimation */, true /* isThumbnail */),
- mWmService.mSurfaceAnimationRunner);
+ if (mThumbnail != null) {
+ thumbnailAdapter = new LocalAnimationAdapter(
+ new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+ getDisplayContent().getDisplayInfo(), durationScale,
+ true /* isAppAnimation */, true /* isThumbnail */),
+ mWmService.mSurfaceAnimationRunner);
+ }
mTransit = transit;
mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
} else {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 650d0be76dc5..ce3fb51aa9f5 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -41,6 +41,8 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -522,6 +524,11 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
mChangeListeners.remove(listener);
}
+ @VisibleForTesting
+ boolean containsListener(ConfigurationContainerListener listener) {
+ return mChangeListeners.contains(listener);
+ }
+
/**
* Must be called when new parent for the container was set.
*/
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8f976e74670d..7a9ff527fc08 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -162,6 +162,7 @@ import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InsetsState.InternalInsetType;
import android.view.MagnificationSpec;
+import android.view.RemoteAnimationDefinition;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -1056,11 +1057,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
*/
void setInsetProvider(@InternalInsetType int type, WindowState win,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
- if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) {
- if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
- return;
- }
- }
mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider);
}
@@ -1091,6 +1087,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mLastWindowForcedOrientation;
}
+ void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+ mAppTransitionController.registerRemoteAnimations(definition);
+ }
+
/**
* Temporarily pauses rotation changes until resumed.
*
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 6d3c69385a09..40063326e76e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -58,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLES
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -2029,7 +2030,8 @@ public class DisplayPolicy {
of.set(displayFrames.mRestricted);
df.set(displayFrames.mRestricted);
pf.set(displayFrames.mRestricted);
- } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
+ } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT
+ || type == TYPE_APPLICATION_OVERLAY) {
// These dialogs are stable to interim decor changes.
cf.set(displayFrames.mStable);
of.set(displayFrames.mStable);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index e49e4c0711bd..e79820338c55 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,13 @@
package com.android.server.wm;
+import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+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 android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +65,7 @@ class InsetsSourceProvider {
*/
private boolean mServerVisible;
+ private final boolean mControllable;
InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
DisplayContent displayContent) {
@@ -66,6 +73,15 @@ class InsetsSourceProvider {
mSource = source;
mDisplayContent = displayContent;
mStateController = stateController;
+
+ final int type = source.getType();
+ if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
+ mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
+ } else if (type == TYPE_IME) {
+ mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
+ } else {
+ mControllable = false;
+ }
}
InsetsSource getSource() {
@@ -73,6 +89,13 @@ class InsetsSourceProvider {
}
/**
+ * @return Whether the current flag configuration allows to control this source.
+ */
+ boolean isControllable() {
+ return mControllable;
+ }
+
+ /**
* Updates the window that currently backs this source.
*
* @param win The window that links to this source.
@@ -91,6 +114,9 @@ class InsetsSourceProvider {
mSource.setFrame(new Rect());
} else {
mWin.setInsetProvider(this);
+ if (mControllingWin != null) {
+ updateControlForTarget(mControllingWin, true /* force */);
+ }
}
}
@@ -113,8 +139,12 @@ class InsetsSourceProvider {
&& !mWin.mGivenInsetsPending);
}
- void updateControlForTarget(@Nullable WindowState target) {
- if (target == mControllingWin) {
+ void updateControlForTarget(@Nullable WindowState target, boolean force) {
+ if (mWin == null) {
+ mControllingWin = target;
+ return;
+ }
+ if (target == mControllingWin && !force) {
return;
}
if (target == null) {
@@ -161,7 +191,7 @@ class InsetsSourceProvider {
}
boolean isClientVisible() {
- return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
+ return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
}
private class ControlAdapter implements AnimationAdapter {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 32dbe96d39f7..bb0cbb1de470 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -163,18 +163,18 @@ class InsetsStateController {
}
private void onControlChanged(int type, @Nullable WindowState win) {
- if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
- return;
- }
final WindowState previous = mTypeWinControlMap.get(type);
if (win == previous) {
return;
}
- final InsetsSourceProvider controller = mControllers.get(type);
+ final InsetsSourceProvider controller = getSourceProvider(type);
if (controller == null) {
return;
}
- controller.updateControlForTarget(win);
+ if (!controller.isControllable()) {
+ return;
+ }
+ controller.updateControlForTarget(win, false /* force */);
if (previous != null) {
removeFromControlMaps(previous, type);
mPendingControlChanged.add(previous);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 177f244129f4..b5be2ac5df98 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -91,9 +91,7 @@ class KeyguardController {
*/
boolean isKeyguardOrAodShowing(int displayId) {
return (mKeyguardShowing || mAodShowing) && !mKeyguardGoingAway
- && (displayId == DEFAULT_DISPLAY
- ? !isDisplayOccluded(DEFAULT_DISPLAY)
- : isShowingOnSecondaryDisplay(displayId));
+ && !isDisplayOccluded(displayId);
}
/**
@@ -101,10 +99,7 @@ class KeyguardController {
* display, false otherwise
*/
boolean isKeyguardShowing(int displayId) {
- return mKeyguardShowing && !mKeyguardGoingAway
- && (displayId == DEFAULT_DISPLAY
- ? !isDisplayOccluded(DEFAULT_DISPLAY)
- : isShowingOnSecondaryDisplay(displayId));
+ return mKeyguardShowing && !mKeyguardGoingAway && !isDisplayOccluded(displayId);
}
/**
@@ -152,14 +147,6 @@ class KeyguardController {
updateKeyguardSleepToken();
}
- private boolean isShowingOnSecondaryDisplay(int displayId) {
- if (mSecondaryDisplayIdsShowing == null) return false;
- for (int showingId : mSecondaryDisplayIdsShowing) {
- if (displayId == showingId) return true;
- }
- return false;
- }
-
/**
* Called when Keyguard is going away.
*
@@ -473,8 +460,16 @@ class KeyguardController {
if (stack.getTopDismissingKeyguardActivity() != null) {
mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
}
+ // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
+ if (mDisplayId != DEFAULT_DISPLAY) {
+ mOccluded |= stack.canShowWithInsecureKeyguard()
+ && controller.canDismissKeyguard();
+ }
+ }
+ // TODO(b/123372519): isShowingDream can only works on default display.
+ if (mDisplayId == DEFAULT_DISPLAY) {
+ mOccluded |= controller.mWindowManager.isShowingDream();
}
- mOccluded |= controller.mWindowManager.isShowingDream();
// TODO(b/113840485): Handle app transition for individual display, and apply occluded
// state change to secondary displays.
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f760b39c5332..5f95691d5307 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -322,8 +322,10 @@ class RemoteAnimationController implements DeathRecipient {
mStartBounds = new Rect(startBounds);
mTmpRect.set(startBounds);
mTmpRect.offsetTo(0, 0);
- mThumbnailAdapter =
- new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+ if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
+ mThumbnailAdapter =
+ new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+ }
} else {
mStartBounds = null;
}
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 9b7214120aed..c4a853dc3483 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,28 +1108,41 @@ class RootActivityContainer extends ConfigurationContainer
return false;
}
+ boolean result = false;
if (targetStack != null && (targetStack.isTopStackOnDisplay()
|| getTopDisplayFocusedStack() == targetStack)) {
- return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+ result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
}
- // Resume all top activities in focused stacks on all displays.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ boolean resumedOnDisplay = false;
final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- final ActivityStack focusedStack = display.getFocusedStack();
- if (focusedStack == null) {
- continue;
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
+ final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
+ if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
+ continue;
+ }
+ if (topRunningActivity.isState(RESUMED)) {
+ // Kick off any lingering app transitions form the MoveTaskToFront operation.
+ stack.executeAppTransition(targetOptions);
+ } else {
+ resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
+ }
}
- final ActivityRecord r = focusedStack.topRunningActivityLocked();
- if (r == null || !r.isState(RESUMED)) {
- focusedStack.resumeTopActivityUncheckedLocked(null, null);
- } else if (r.isState(RESUMED)) {
- // Kick off any lingering app transitions form the MoveTaskToFront operation.
- focusedStack.executeAppTransition(targetOptions);
+ if (!resumedOnDisplay) {
+ // In cases when there are no valid activities (e.g. device just booted or launcher
+ // crashed) it's possible that nothing was resumed on a display. Requesting resume
+ // of top activity in focused stack explicitly will make sure that at least home
+ // activity is started and resumed, and no recursion occurs.
+ final ActivityStack focusedStack = display.getFocusedStack();
+ if (focusedStack != null) {
+ focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+ }
}
}
- return false;
+ return result;
}
void applySleepTokens(boolean applyToStacks) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a7dd55b8a160..476bd6e9abe9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRA
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.EMPTY;
+import static android.view.SurfaceControl.METADATA_TASK_ID;
import static com.android.server.EventLogTags.WM_TASK_REMOVED;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
@@ -643,6 +644,11 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta
return getAppAnimationLayer(ANIMATION_LAYER_HOME);
}
+ @Override
+ SurfaceControl.Builder makeSurface() {
+ return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
+ }
+
boolean isTaskAnimating() {
final RecentsAnimationController recentsAnim = mWmService.getRecentsAnimationController();
if (recentsAnim != null) {
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 69dcaf473b12..0529ed128130 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,6 +698,14 @@ class TaskRecord extends ConfigurationContainer {
return false;
}
+ final boolean toTopOfStack = position == MAX_VALUE;
+ if (toTopOfStack && toStack.getResumedActivity() != null
+ && toStack.topRunningActivityLocked() != null) {
+ // Pause the resumed activity on the target stack while re-parenting task on top of it.
+ toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+ null /* resuming */, false /* pauseImmediately */);
+ }
+
final int toStackWindowingMode = toStack.getWindowingMode();
final ActivityRecord topActivity = getTopActivity();
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
index 7dd7c4f5c958..775d5b2fb79a 100644
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -53,13 +53,15 @@ public class WindowChangeAnimationSpec implements AnimationSpec {
private Animation mAnimation;
private final boolean mIsThumbnail;
+ static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION;
+
public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
- long duration, boolean isAppAnimation, boolean isThumbnail) {
+ float durationScale, boolean isAppAnimation, boolean isThumbnail) {
mStartBounds = new Rect(startBounds);
mEndBounds = new Rect(endBounds);
mIsAppAnimation = isAppAnimation;
mIsThumbnail = isThumbnail;
- createBoundsInterpolator(duration, displayInfo);
+ createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5eff7d8a8553..e6581df233ef 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2817,7 +2817,7 @@ public class WindowManagerService extends IWindowManager.Stub
public boolean isShowingDream() {
synchronized (mGlobalLock) {
- // TODO: fix this when dream can be shown on non-default display.
+ // TODO(b/123372519): Fix this when dream can be shown on non-default display.
return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index c38a974ac774..07f26b4c70bb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -52,6 +52,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.Watchdog;
@@ -330,6 +331,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mRequiredAbi;
}
+ /** Returns ID of display overriding the configuration for this process, or
+ * INVALID_DISPLAY if no display is overriding. */
+ @VisibleForTesting
+ int getDisplayId() {
+ return mDisplayId;
+ }
+
public void setDebugging(boolean debugging) {
mDebugging = debugging;
}
@@ -761,15 +769,15 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
activityDisplay.registerConfigurationChangeListener(this);
}
- private void unregisterDisplayConfigurationListenerLocked() {
+ @VisibleForTesting
+ void unregisterDisplayConfigurationListenerLocked() {
if (mDisplayId == INVALID_DISPLAY) {
return;
}
final ActivityDisplay activityDisplay =
mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId);
if (activityDisplay != null) {
- mAtm.mRootActivityContainer.getActivityDisplay(
- mDisplayId).unregisterConfigurationChangeListener(this);
+ activityDisplay.unregisterConfigurationChangeListener(this);
}
mDisplayId = INVALID_DISPLAY;
}
@@ -867,6 +875,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
public boolean appNotResponding(String info, Runnable killAppCallback,
Runnable serviceTimeoutCallback) {
+ Runnable targetRunnable = null;
synchronized (mAtm.mGlobalLock) {
if (mAtm.mController == null) {
return false;
@@ -877,18 +886,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
int res = mAtm.mController.appNotResponding(mName, mPid, info);
if (res != 0) {
if (res < 0 && mPid != MY_PID) {
- killAppCallback.run();
+ targetRunnable = killAppCallback;
} else {
- serviceTimeoutCallback.run();
+ targetRunnable = serviceTimeoutCallback;
}
- return true;
}
} catch (RemoteException e) {
mAtm.mController = null;
Watchdog.getInstance().setActivityController(null);
+ return false;
}
- return false;
}
+ if (targetRunnable != null) {
+ targetRunnable.run();
+ return true;
+ }
+ return false;
}
public void onTopProcChanged() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ce5eb8476764..4f120100693a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4367,6 +4367,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
void startAnimation(Animation anim) {
+
+ // If we are an inset provider, all our animations are driven by the inset client.
+ if (mInsetProvider != null && mInsetProvider.isControllable()) {
+ return;
+ }
+
final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
displayInfo.appWidth, displayInfo.appHeight);
@@ -4380,6 +4386,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
private void startMoveAnimation(int left, int top) {
+
+ // If we are an inset provider, all our animations are driven by the inset client.
+ if (mInsetProvider != null && mInsetProvider.isControllable()) {
+ return;
+ }
+
if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this);
final Point oldPosition = new Point();
final Point newPosition = new Point();
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index c2a8e7efb5a5..dea3597989be 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -18,6 +18,8 @@ package com.android.server.wm;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -35,7 +37,6 @@ import android.os.IBinder;
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowContentFrameStats;
@@ -103,7 +104,8 @@ class WindowSurfaceController {
.setBufferSize(w, h)
.setFormat(format)
.setFlags(flags)
- .setMetadata(windowType, ownerUid);
+ .setMetadata(METADATA_WINDOW_TYPE, windowType)
+ .setMetadata(METADATA_OWNER_UID, ownerUid);
mSurfaceControl = b.build();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index c90113f4b44e..26f6d7428fcc 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -39,37 +39,7 @@ using Type = ::android::hardware::light::V2_0::Type;
template<typename T>
using Return = ::android::hardware::Return<T>;
-class LightHal {
-private:
- static sp<ILight> sLight;
- static bool sLightInit;
-
- LightHal() {}
-
-public:
- static void disassociate() {
- sLightInit = false;
- sLight = nullptr;
- }
-
- static sp<ILight> associate() {
- if ((sLight == nullptr && !sLightInit) ||
- (sLight != nullptr && !sLight->ping().isOk())) {
- // will return the hal if it exists the first time.
- sLight = ILight::getService();
- sLightInit = true;
-
- if (sLight == nullptr) {
- ALOGE("Unable to get ILight interface.");
- }
- }
-
- return sLight;
- }
-};
-
-sp<ILight> LightHal::sLight = nullptr;
-bool LightHal::sLightInit = false;
+static bool sLightSupported = true;
static bool validate(jint light, jint flash, jint brightness) {
bool valid = true;
@@ -134,7 +104,6 @@ static void processReturn(
const LightState &state) {
if (!ret.isOk()) {
ALOGE("Failed to issue set light command.");
- LightHal::disassociate();
return;
}
@@ -164,13 +133,11 @@ static void setLight_native(
jint offMS,
jint brightnessMode) {
- if (!validate(light, flashMode, brightnessMode)) {
+ if (!sLightSupported) {
return;
}
- sp<ILight> hal = LightHal::associate();
-
- if (hal == nullptr) {
+ if (!validate(light, flashMode, brightnessMode)) {
return;
}
@@ -180,6 +147,11 @@ static void setLight_native(
{
android::base::Timer t;
+ sp<ILight> hal = ILight::getService();
+ if (hal == nullptr) {
+ sLightSupported = false;
+ return;
+ }
Return<Status> ret = hal->setLight(type, state);
processReturn(ret, type, state);
if (t.duration() > 50ms) ALOGD("Excessive delay setting light");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index d8225b38487c..2bf6f357bec8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -108,12 +108,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
@Override
- public void addCrossProfileCalendarPackage(ComponentName admin, String packageName) {
- }
-
- @Override
- public boolean removeCrossProfileCalendarPackage(ComponentName admin, String packageName) {
- return false;
+ public void setCrossProfileCalendarPackages(ComponentName admin, List<String> packageNames) {
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 54053a896df9..f79f9bc4ef86 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -70,9 +70,11 @@ import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AF
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
+import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+
import static android.provider.Telephony.Carriers.DPC_URI;
import static android.provider.Telephony.Carriers.ENFORCE_KEY;
import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
@@ -81,10 +83,11 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
- .ADMIN_TYPE_DEVICE_OWNER;
-import static com.android.server.devicepolicy.TransferOwnershipMetadataManager
- .ADMIN_TYPE_PROFILE_OWNER;
+
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER;
+import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER;
+
+
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -237,11 +240,11 @@ import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.StatLogger;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.internal.util.StatLogger;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -949,8 +952,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
"metered_data_disabled_packages";
private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
"cross-profile-calendar-packages";
- private static final String TAG_PACKAGE = "package";
-
+ private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
+ "cross-profile-calendar-packages-null";
DeviceAdminInfo info;
@@ -1072,7 +1075,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
String endUserSessionMessage = null;
// The whitelist of packages that can access cross profile calendar APIs.
- final Set<String> mCrossProfileCalendarPackages = new ArraySet<>();
+ // This whitelist should be in default an empty list, which indicates that no package
+ // is whitelisted.
+ List<String> mCrossProfileCalendarPackages = Collections.emptyList();
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
@@ -1343,11 +1348,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
out.text(endUserSessionMessage);
out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
}
- if (!mCrossProfileCalendarPackages.isEmpty()) {
- out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
- writeAttributeValuesToXml(
- out, TAG_PACKAGE, mCrossProfileCalendarPackages);
- out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
+ if (mCrossProfileCalendarPackages == null) {
+ out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+ out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+ } else {
+ writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
+ mCrossProfileCalendarPackages);
}
}
@@ -1542,8 +1548,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Log.w(LOG_TAG, "Missing text when loading end session message");
}
} else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
- readAttributeValues(
- parser, TAG_PACKAGE, mCrossProfileCalendarPackages);
+ mCrossProfileCalendarPackages = readPackageList(parser, tag);
+ } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
+ mCrossProfileCalendarPackages = null;
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1759,8 +1766,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
pw.print(prefix); pw.println("parentAdmin:");
parentAdmin.dump(prefix + " ", pw);
}
- pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
- pw.println(mCrossProfileCalendarPackages);
+ if (mCrossProfileCalendarPackages != null) {
+ pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
+ pw.println(mCrossProfileCalendarPackages);
+ }
}
}
@@ -6362,7 +6371,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private void forceWipeUser(int userId, String wipeReasonForUser) {
+ private void forceWipeUser(int userId, String wipeReasonForUser, boolean wipeSilently) {
boolean success = false;
try {
IActivityManager am = mInjector.getIActivityManager();
@@ -6373,7 +6382,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
success = mUserManagerInternal.removeUserEvenWhenDisallowed(userId);
if (!success) {
Slog.w(LOG_TAG, "Couldn't remove user " + userId);
- } else if (isManagedProfile(userId) && !TextUtils.isEmpty(wipeReasonForUser)) {
+ } else if (isManagedProfile(userId) && !wipeSilently) {
sendWipeProfileNotification(wipeReasonForUser);
}
} catch (RemoteException re) {
@@ -6388,6 +6397,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
+ Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
final ActiveAdmin admin;
@@ -6447,7 +6457,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
internalReason,
/*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
} else {
- forceWipeUser(userId, wipeReasonForUser);
+ forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0);
}
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -13988,55 +13998,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public void addCrossProfileCalendarPackage(ComponentName who, String packageName) {
+ public void setCrossProfileCalendarPackages(ComponentName who, List<String> packageNames) {
if (!mHasFeature) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (admin.mCrossProfileCalendarPackages.add(packageName)) {
- saveSettingsLocked(mInjector.userHandleGetCallingUserId());
- }
+ admin.mCrossProfileCalendarPackages = packageNames;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+ .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES)
.setAdmin(who)
- .setStrings(packageName)
+ .setStrings(packageNames == null ? null
+ : packageNames.toArray(new String[packageNames.size()]))
.write();
}
@Override
- public boolean removeCrossProfileCalendarPackage(ComponentName who, String packageName) {
- if (!mHasFeature) {
- return false;
- }
- Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
-
- boolean isRemoved = false;
- synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- isRemoved = admin.mCrossProfileCalendarPackages.remove(packageName);
- if (isRemoved) {
- saveSettingsLocked(mInjector.userHandleGetCallingUserId());
- }
- }
- if (isRemoved) {
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
- .setAdmin(who)
- .setStrings(packageName)
- .write();
- }
- return isRemoved;
- }
-
- @Override
public List<String> getCrossProfileCalendarPackages(ComponentName who) {
if (!mHasFeature) {
return Collections.emptyList();
@@ -14046,7 +14028,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
@@ -14062,6 +14044,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
+ if (admin.mCrossProfileCalendarPackages == null) {
+ return true;
+ }
return admin.mCrossProfileCalendarPackages.contains(packageName);
}
}
@@ -14077,7 +14062,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
return Collections.emptyList();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 65eaf5541088..586136802619 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1561,6 +1561,12 @@ public final class SystemServer {
traceEnd();
}
+ // Grants default permissions and defines roles
+ traceBeginAndSlog("StartRoleManagerService");
+ mSystemServiceManager.startService(new RoleManagerService(
+ mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
+ traceEnd();
+
// We need to always start this service, regardless of whether the
// FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
// of initializing various settings. It will internally modify its behavior
@@ -2011,12 +2017,6 @@ public final class SystemServer {
}
traceEnd();
- // Grants default permissions and defines roles
- traceBeginAndSlog("StartRoleManagerService");
- mSystemServiceManager.startService(new RoleManagerService(
- mSystemContext, new LegacyRoleResolutionPolicy(mSystemContext)));
- traceEnd();
-
// No dependency on Webview preparation in system server. But this should
// be completed before allowing 3rd party
final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
deleted file mode 100644
index a61c2efd64da..000000000000
--- a/services/net/java/android/net/ip/IpClient.java
+++ /dev/null
@@ -1,320 +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.net.ip;
-
-import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
-
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.ProxyInfo;
-import android.net.StaticIpConfiguration;
-import android.net.apf.ApfCapabilities;
-import android.os.ConditionVariable;
-import android.os.INetworkManagementService;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated.
- * @hide
- */
-public class IpClient {
- private static final String TAG = IpClient.class.getSimpleName();
- private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000;
-
- public static final String DUMP_ARG = "ipclient";
-
- private final ConditionVariable mIpClientCv;
- private final ConditionVariable mShutdownCv;
-
- private volatile IIpClient mIpClient;
-
- /**
- * @see IpClientCallbacks
- */
- public static class Callback extends IpClientCallbacks {}
-
- /**
- * IpClient callback that allows clients to block until provisioning is complete.
- */
- public static class WaitForProvisioningCallback extends Callback {
- private final ConditionVariable mCV = new ConditionVariable();
- private LinkProperties mCallbackLinkProperties;
-
- /**
- * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
- * {@link #onProvisioningFailure(LinkProperties)} is called.
- */
- public LinkProperties waitForProvisioning() {
- mCV.block();
- return mCallbackLinkProperties;
- }
-
- @Override
- public void onProvisioningSuccess(LinkProperties newLp) {
- mCallbackLinkProperties = newLp;
- mCV.open();
- }
-
- @Override
- public void onProvisioningFailure(LinkProperties newLp) {
- mCallbackLinkProperties = null;
- mCV.open();
- }
- }
-
- private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
- /**
- * Create a new IpClientCallbacksProxy.
- */
- CallbackImpl(IpClientCallbacks cb) {
- super(cb);
- }
-
- @Override
- public void onIpClientCreated(IIpClient ipClient) {
- mIpClient = ipClient;
- mIpClientCv.open();
- super.onIpClientCreated(ipClient);
- }
-
- @Override
- public void onQuit() {
- mShutdownCv.open();
- super.onQuit();
- }
- }
-
- /**
- * Create a new IpClient.
- */
- public IpClient(Context context, String iface, Callback callback) {
- mIpClientCv = new ConditionVariable(false);
- mShutdownCv = new ConditionVariable(false);
-
- IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
- }
-
- /**
- * @see IpClient#IpClient(Context, String, IpClient.Callback)
- */
- public IpClient(Context context, String iface, Callback callback,
- INetworkManagementService nms) {
- this(context, iface, callback);
- }
-
- private interface IpClientAction {
- void useIpClient(IIpClient ipClient) throws RemoteException;
- }
-
- private void doWithIpClient(IpClientAction action) {
- mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
- try {
- action.useIpClient(mIpClient);
- } catch (RemoteException e) {
- Log.e(TAG, "Error communicating with IpClient", e);
- }
- }
-
- /**
- * Notify IpClient that PreDhcpAction is completed.
- */
- public void completedPreDhcpAction() {
- doWithIpClient(c -> c.completedPreDhcpAction());
- }
-
- /**
- * Confirm the provisioning configuration.
- */
- public void confirmConfiguration() {
- doWithIpClient(c -> c.confirmConfiguration());
- }
-
- /**
- * Notify IpClient that packet filter read is complete.
- */
- public void readPacketFilterComplete(byte[] data) {
- doWithIpClient(c -> c.readPacketFilterComplete(data));
- }
-
- /**
- * Shutdown the IpClient altogether.
- */
- public void shutdown() {
- doWithIpClient(c -> c.shutdown());
- }
-
- /**
- * Start the IpClient provisioning.
- */
- public void startProvisioning(ProvisioningConfiguration config) {
- doWithIpClient(c -> c.startProvisioning(config.toStableParcelable()));
- }
-
- /**
- * Stop the IpClient.
- */
- public void stop() {
- doWithIpClient(c -> c.stop());
- }
-
- /**
- * Set the IpClient TCP buffer sizes.
- */
- public void setTcpBufferSizes(String tcpBufferSizes) {
- doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes));
- }
-
- /**
- * Set the IpClient HTTP proxy.
- */
- public void setHttpProxy(ProxyInfo proxyInfo) {
- doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo)));
- }
-
- /**
- * Set the IpClient multicast filter.
- */
- public void setMulticastFilter(boolean enabled) {
- doWithIpClient(c -> c.setMulticastFilter(enabled));
- }
-
- /**
- * Dump IpClient logs.
- */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args));
- }
-
- /**
- * Block until IpClient shutdown.
- */
- public void awaitShutdown() {
- mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
- }
-
- /**
- * Create a new ProvisioningConfiguration.
- */
- public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
- return new ProvisioningConfiguration.Builder();
- }
-
- /**
- * TODO: remove after migrating clients to use the shared configuration class directly.
- * @see android.net.shared.ProvisioningConfiguration
- */
- public static class ProvisioningConfiguration
- extends android.net.shared.ProvisioningConfiguration {
- public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) {
- super(other);
- }
-
- /**
- * @see android.net.shared.ProvisioningConfiguration.Builder
- */
- public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder {
- // Override all methods to have a return type matching this Builder
- @Override
- public Builder withoutIPv4() {
- super.withoutIPv4();
- return this;
- }
-
- @Override
- public Builder withoutIPv6() {
- super.withoutIPv6();
- return this;
- }
-
- @Override
- public Builder withoutMultinetworkPolicyTracker() {
- super.withoutMultinetworkPolicyTracker();
- return this;
- }
-
- @Override
- public Builder withoutIpReachabilityMonitor() {
- super.withoutIpReachabilityMonitor();
- return this;
- }
-
- @Override
- public Builder withPreDhcpAction() {
- super.withPreDhcpAction();
- return this;
- }
-
- @Override
- public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
- super.withPreDhcpAction(dhcpActionTimeoutMs);
- return this;
- }
-
- @Override
- public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
- super.withStaticConfiguration(staticConfig);
- return this;
- }
-
- @Override
- public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
- super.withApfCapabilities(apfCapabilities);
- return this;
- }
-
- @Override
- public Builder withProvisioningTimeoutMs(int timeoutMs) {
- super.withProvisioningTimeoutMs(timeoutMs);
- return this;
- }
-
- @Override
- public Builder withRandomMacAddress() {
- super.withRandomMacAddress();
- return this;
- }
-
- @Override
- public Builder withStableMacAddress() {
- super.withStableMacAddress();
- return this;
- }
-
- @Override
- public Builder withNetwork(Network network) {
- super.withNetwork(network);
- return this;
- }
-
- @Override
- public Builder withDisplayName(String displayName) {
- super.withDisplayName(displayName);
- return this;
- }
-
- @Override
- public ProvisioningConfiguration build() {
- return new ProvisioningConfiguration(mConfig);
- }
- }
- }
-}
diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
index 2c368c81523e..00073503886a 100644
--- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
+++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
@@ -73,7 +73,7 @@ public final class IpConfigurationParcelableUtil {
public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) {
if (results == null) return null;
final DhcpResultsParcelable p = new DhcpResultsParcelable();
- p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results);
+ p.baseConfiguration = toStableParcelable(results.toStaticIpConfiguration());
p.leaseDuration = results.leaseDuration;
p.mtu = results.mtu;
p.serverAddress = parcelAddress(results.serverAddress);
diff --git a/services/net/java/android/net/shared/NetworkObserverRegistry.java b/services/net/java/android/net/shared/NetworkObserverRegistry.java
new file mode 100644
index 000000000000..36945f5de2c5
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkObserverRegistry.java
@@ -0,0 +1,255 @@
+/*
+ * 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.net.shared;
+
+import static android.Manifest.permission.NETWORK_STACK;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.INetworkManagementEventObserver;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+
+/**
+ * A class for reporting network events to clients.
+ *
+ * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to
+ * all INetworkManagementEventObserver objects that have registered with it.
+ *
+ * TODO: Make the notifyXyz methods protected once subclasses (e.g., the NetworkManagementService
+ * subclass) no longer call them directly.
+ *
+ * TODO: change from RemoteCallbackList to direct in-process callbacks.
+ */
+public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub {
+
+ private final Context mContext;
+ private final Handler mDaemonHandler;
+ private static final String TAG = "NetworkObserverRegistry";
+
+ /**
+ * Constructs a new instance and registers it with netd.
+ * This method should only be called once since netd will reject multiple registrations from
+ * the same process.
+ */
+ public NetworkObserverRegistry(Context context, Handler handler, INetd netd)
+ throws RemoteException {
+ mContext = context;
+ mDaemonHandler = handler;
+ netd.registerUnsolicitedEventListener(this);
+ }
+
+ private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
+ new RemoteCallbackList<>();
+
+ /**
+ * Registers the specified observer and start sending callbacks to it.
+ * This method may be called on any thread.
+ */
+ public void registerObserver(INetworkManagementEventObserver observer) {
+ mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+ mObservers.register(observer);
+ }
+
+ /**
+ * Unregisters the specified observer and stop sending callbacks to it.
+ * This method may be called on any thread.
+ */
+ public void unregisterObserver(INetworkManagementEventObserver observer) {
+ mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+ mObservers.unregister(observer);
+ }
+
+ @FunctionalInterface
+ private interface NetworkManagementEventCallback {
+ void sendCallback(INetworkManagementEventObserver o) throws RemoteException;
+ }
+
+ private void invokeForAllObservers(NetworkManagementEventCallback eventCallback) {
+ final int length = mObservers.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ eventCallback.sendCallback(mObservers.getBroadcastItem(i));
+ } catch (RemoteException | RuntimeException e) {
+ }
+ }
+ } finally {
+ mObservers.finishBroadcast();
+ }
+ }
+
+ /**
+ * Notify our observers of a change in the data activity state of the interface
+ */
+ public void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+ int uid, boolean fromRadio) {
+ invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
+ Integer.toString(type), isActive, tsNanos));
+ }
+
+ @Override
+ public void onInterfaceClassActivityChanged(boolean isActive,
+ int label, long timestamp, int uid) throws RemoteException {
+ final long timestampNanos;
+ if (timestamp <= 0) {
+ timestampNanos = SystemClock.elapsedRealtimeNanos();
+ } else {
+ timestampNanos = timestamp;
+ }
+ mDaemonHandler.post(() -> notifyInterfaceClassActivity(label, isActive,
+ timestampNanos, uid, false));
+ }
+
+ /**
+ * Notify our observers of a limit reached.
+ */
+ @Override
+ public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyLimitReached(alertName, ifName));
+ }
+
+ /**
+ * Notify our observers of a limit reached.
+ */
+ public void notifyLimitReached(String limitName, String iface) {
+ invokeForAllObservers(o -> o.limitReached(limitName, iface));
+ }
+
+ @Override
+ public void onInterfaceDnsServerInfo(String ifName,
+ long lifetime, String[] servers) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceDnsServerInfo(ifName, lifetime, servers));
+ }
+
+ /**
+ * Notify our observers of DNS server information received.
+ */
+ public void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+ invokeForAllObservers(o -> o.interfaceDnsServerInfo(iface, lifetime, addresses));
+ }
+
+ @Override
+ public void onInterfaceAddressUpdated(String addr,
+ String ifName, int flags, int scope) throws RemoteException {
+ final LinkAddress address = new LinkAddress(addr, flags, scope);
+ mDaemonHandler.post(() -> notifyAddressUpdated(ifName, address));
+ }
+
+ /**
+ * Notify our observers of a new or updated interface address.
+ */
+ public void notifyAddressUpdated(String iface, LinkAddress address) {
+ invokeForAllObservers(o -> o.addressUpdated(iface, address));
+ }
+
+ @Override
+ public void onInterfaceAddressRemoved(String addr,
+ String ifName, int flags, int scope) throws RemoteException {
+ final LinkAddress address = new LinkAddress(addr, flags, scope);
+ mDaemonHandler.post(() -> notifyAddressRemoved(ifName, address));
+ }
+
+ /**
+ * Notify our observers of a deleted interface address.
+ */
+ public void notifyAddressRemoved(String iface, LinkAddress address) {
+ invokeForAllObservers(o -> o.addressRemoved(iface, address));
+ }
+
+
+ @Override
+ public void onInterfaceAdded(String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceAdded(ifName));
+ }
+
+ /**
+ * Notify our observers of an interface addition.
+ */
+ public void notifyInterfaceAdded(String iface) {
+ invokeForAllObservers(o -> o.interfaceAdded(iface));
+ }
+
+ @Override
+ public void onInterfaceRemoved(String ifName) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceRemoved(ifName));
+ }
+
+ /**
+ * Notify our observers of an interface removal.
+ */
+ public void notifyInterfaceRemoved(String iface) {
+ invokeForAllObservers(o -> o.interfaceRemoved(iface));
+ }
+
+ @Override
+ public void onInterfaceChanged(String ifName, boolean up) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceStatusChanged(ifName, up));
+ }
+
+ /**
+ * Notify our observers of an interface status change
+ */
+ public void notifyInterfaceStatusChanged(String iface, boolean up) {
+ invokeForAllObservers(o -> o.interfaceStatusChanged(iface, up));
+ }
+
+ @Override
+ public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException {
+ mDaemonHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up));
+ }
+
+ /**
+ * Notify our observers of an interface link state change
+ * (typically, an Ethernet cable has been plugged-in or unplugged).
+ */
+ public void notifyInterfaceLinkStateChanged(String iface, boolean up) {
+ invokeForAllObservers(o -> o.interfaceLinkStateChanged(iface, up));
+ }
+
+ @Override
+ public void onRouteChanged(boolean updated,
+ String route, String gateway, String ifName) throws RemoteException {
+ final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+ ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+ ifName);
+ mDaemonHandler.post(() -> notifyRouteChange(updated, processRoute));
+ }
+
+ /**
+ * Notify our observers of a route change.
+ */
+ public void notifyRouteChange(boolean updated, RouteInfo route) {
+ if (updated) {
+ invokeForAllObservers(o -> o.routeUpdated(route));
+ } else {
+ invokeForAllObservers(o -> o.routeRemoved(route));
+ }
+ }
+
+ @Override
+ public void onStrictCleartextDetected(int uid, String hex) throws RemoteException {
+ // Don't do anything here because this is not a method of INetworkManagementEventObserver.
+ // Only the NMS subclass will implement this.
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index d91ce39ea92c..de7d77d4b963 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -158,7 +158,7 @@ public class MagnificationGestureHandlerTest {
boolean detectShortcutTrigger) {
MagnificationGestureHandler h = new MagnificationGestureHandler(
mContext, mMagnificationController,
- detectTripleTap, detectShortcutTrigger);
+ detectTripleTap, detectShortcutTrigger, DISPLAY_0);
mHandler = new TestHandler(h.mDetectingState, mClock) {
@Override
protected String messageToString(Message m) {
diff --git a/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java
new file mode 100644
index 000000000000..1a231cf56921
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AppCompactorTest.java
@@ -0,0 +1,379 @@
+/*
+ * 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.am;
+
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_ACTION_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_1;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_2;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_3;
+import static android.provider.DeviceConfig.ActivityManager.KEY_COMPACT_THROTTLE_4;
+import static android.provider.DeviceConfig.ActivityManager.KEY_USE_COMPACTION;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.AppCompactor.compactActionIntToString;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.appop.AppOpsService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link AppCompactor}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AppCompactorTest
+ */
+@RunWith(AndroidJUnit4.class)
+public final class AppCompactorTest {
+
+ @Mock private AppOpsService mAppOpsService;
+ private AppCompactor mCompactorUnderTest;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private CountDownLatch mCountDown;
+
+ private static void clearDeviceConfig() throws IOException {
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_USE_COMPACTION);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_ACTION_1);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_ACTION_2);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_1);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_2);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_3);
+ uiDevice.executeShellCommand(
+ "device_config delete activity_manager " + KEY_COMPACT_THROTTLE_4);
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ clearDeviceConfig();
+ mHandlerThread = new HandlerThread("");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ ActivityManagerService ams = new ActivityManagerService(new TestInjector());
+ mCompactorUnderTest = new AppCompactor(ams,
+ new AppCompactor.PropertyChangedCallbackForTest() {
+ @Override
+ public void onPropertyChanged() {
+ if (mCountDown != null) {
+ mCountDown.countDown();
+ }
+ }
+ });
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ mHandlerThread.quit();
+ mCountDown = null;
+ clearDeviceConfig();
+ }
+
+ @Test
+ public void init_setsDefaults() {
+ mCompactorUnderTest.init();
+ assertThat(mCompactorUnderTest.useCompaction(),
+ is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+ assertThat(mCompactorUnderTest.mCompactActionSome, is(
+ compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull, is(
+ compactActionIntToString(mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2)));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ }
+
+ @Test
+ public void init_withDeviceConfigSetsParameters() {
+ // When the DeviceConfig already has a flag value stored (note this test will need to
+ // change if the default value changes from false).
+ assertThat(mCompactorUnderTest.DEFAULT_USE_COMPACTION, is(false));
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "true", false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1,
+ Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2,
+ Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+
+ // Then calling init will read and set that flag.
+ mCompactorUnderTest.init();
+ assertThat(mCompactorUnderTest.useCompaction(), is(true));
+ assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true));
+
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 3) + 1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 3) + 1)));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1));
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+ assertThat(mCompactorUnderTest.useCompaction(),
+ is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+ // When we call init and change some the flag value...
+ mCompactorUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "true", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then that new flag value is updated in the implementation.
+ assertThat(mCompactorUnderTest.useCompaction(), is(true));
+ assertThat(mCompactorUnderTest.mCompactionThread.isAlive(), is(true));
+
+ // And again, setting the flag the other way.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "false", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.useCompaction(), is(false));
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ assertThat(mCompactorUnderTest.useCompaction(),
+ is(mCompactorUnderTest.DEFAULT_USE_COMPACTION));
+ mCompactorUnderTest.init();
+
+ // When we push an invalid flag value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_USE_COMPACTION, "foobar", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then we set the default.
+ assertThat(mCompactorUnderTest.useCompaction(), is(AppCompactor.DEFAULT_USE_COMPACTION));
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When we override new values for the compaction action with reasonable values...
+
+ // There are three possible values for compactAction[Some|Full].
+ for (int i = 1; i < 4; i++) {
+ mCountDown = new CountDownLatch(2);
+ int expectedSome = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_1 + i) % 3 + 1;
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+ int expectedFull = (mCompactorUnderTest.DEFAULT_COMPACT_ACTION_2 + i) % 3 + 1;
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then the updates are reflected in the flags.
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString(expectedSome)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString(expectedFull)));
+ }
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When we override new values for the compaction action with bad values ...
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1, "foo", false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then the default values are reflected in the flag
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)));
+
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_1, "", false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_ACTION_2, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ assertThat(mCompactorUnderTest.mCompactActionSome,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1)));
+ assertThat(mCompactorUnderTest.mCompactActionFull,
+ is(compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2)));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When we override new reasonable throttle values after init...
+ mCountDown = new CountDownLatch(4);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4,
+ Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+
+ // Then those flags values are reflected in the compactor.
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChangesBadValues()
+ throws IOException, InterruptedException {
+ mCompactorUnderTest.init();
+
+ // When one of the throttles is overridden with a bad value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_1, "foo", false);
+ // Then all the throttles have the defaults set.
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ clearDeviceConfig();
+
+ // Repeat for each of the throttle keys.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ clearDeviceConfig();
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_3, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ clearDeviceConfig();
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.ActivityManager.NAMESPACE,
+ KEY_COMPACT_THROTTLE_4, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS), is(true));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_1));
+ assertThat(mCompactorUnderTest.mCompactThrottleSomeFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_2));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullSome,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_3));
+ assertThat(mCompactorUnderTest.mCompactThrottleFullFull,
+ is(AppCompactor.DEFAULT_COMPACT_THROTTLE_4));
+ }
+
+ private class TestInjector extends Injector {
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index ac4a5fe90c06..a3f36b720398 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -24,11 +24,15 @@ import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.annotation.UserIdInt;
import android.app.backup.BackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -39,21 +43,21 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
import android.util.SparseArray;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.test.FakeSettingsProvider;
-
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -99,10 +103,6 @@ public class TrampolineTest {
@Mock
private Context mContextMock;
@Mock
- private File mSuppressFileMock;
- @Mock
- private File mSuppressFileParentMock;
- @Mock
private IBinder mAgentMock;
@Mock
private ParcelFileDescriptor mParcelFileDescriptorMock;
@@ -114,181 +114,232 @@ public class TrampolineTest {
private IBackupManagerMonitor mBackupManagerMonitorMock;
@Mock
private PrintWriter mPrintWriterMock;
+ @Mock
+ private UserManager mUserManagerMock;
+ @Mock
+ private UserInfo mUserInfoMock;
private FileDescriptor mFileDescriptorStub = new FileDescriptor();
private TrampolineTestable mTrampoline;
- private MockContentResolver mContentResolver;
+ private File mTestDir;
+ private File mSuppressFile;
+ private File mActivatedFile;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mUserId = NON_USER_SYSTEM;
+ mUserId = UserHandle.USER_SYSTEM;
SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>();
- serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService);
+ serviceUsers.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService);
when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers);
+ when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
+ when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
+
TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock;
TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM;
TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
TrampolineTestable.sBackupDisabled = false;
+ TrampolineTestable.sUserManagerMock = mUserManagerMock;
+
+ mTestDir = InstrumentationRegistry.getContext().getFilesDir();
+ mTestDir.mkdirs();
- when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
+ mSuppressFile = new File(mTestDir, "suppress");
+ TrampolineTestable.sSuppressFile = mSuppressFile;
+
+ mActivatedFile = new File(mTestDir, "activate-" + NON_USER_SYSTEM);
+ TrampolineTestable.sActivatedFiles.append(NON_USER_SYSTEM, mActivatedFile);
mTrampoline = new TrampolineTestable(mContextMock);
+ }
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContextMock.getContentResolver()).thenReturn(mContentResolver);
+ @After
+ public void tearDown() throws Exception {
+ mSuppressFile.delete();
+ mActivatedFile.delete();
}
@Test
- public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
+ public void initializeService_successfullyInitializesBackupService() {
mTrampoline.initializeService();
- mTrampoline.unlockUser(UserHandle.USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).startServiceForUser(UserHandle.USER_SYSTEM);
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
- mTrampoline.initializeService();
+ public void initializeService_globallyDisabled_nonInitialized() {
+ TrampolineTestable.sBackupDisabled = true;
+ TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
- mTrampoline.unlockUser(NON_USER_SYSTEM);
+ trampoline.initializeService();
- verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM);
+ assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
+ public void initializeService_doesNotStartServiceForUsers() {
mTrampoline.initializeService();
- mTrampoline.unlockUser(NON_USER_SYSTEM);
+ verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt());
+ }
- verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM);
+ @Test
+ public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() {
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void stopUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
+ public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception {
mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- mTrampoline.stopUser(UserHandle.USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).stopServiceForUser(UserHandle.USER_SYSTEM);
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void stopUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
+ public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception {
mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
- mTrampoline.stopUser(NON_USER_SYSTEM);
-
- verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM);
+ assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() {
- Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
-
+ public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated()
+ throws Exception {
mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- mTrampoline.stopUser(NON_USER_SYSTEM);
-
- verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM);
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
- public void initializeService_successfullyInitializesBackupService() {
+ public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated()
+ throws Exception {
mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ // Don't activate non-system user.
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
- public void initializeService_globallyDisabled_nonInitialized() {
- TrampolineTestable.sBackupDisabled = true;
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
-
- trampoline.initializeService();
+ public void
+ isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated()
+ throws Exception {
+ mTrampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
- // Verify that BackupManagerService is not initialized if suppress file exists.
@Test
- public void initializeService_suppressFileExists_nonInitialized() throws Exception {
- TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
- trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM);
-
+ public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() {
+ mTrampoline.initializeService();
+ TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
- trampoline.initializeService();
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void initializeService_doesNotStartServiceForUsers() {
+ public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() {
mTrampoline.initializeService();
+ TrampolineTestable.sCallingUid = Process.ROOT_UID;
- verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt());
- }
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
- @Test
- public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() {
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
}
@Test
- public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
+ public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() {
+ mTrampoline.initializeService();
+ TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
- assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ try {
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
}
@Test
- public void setBackupServiceActive_callerSystemUid_serviceCreated() {
+ public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() {
+ when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+ mTrampoline.initializeService();
TrampolineTestable.sCallingUid = Process.SYSTEM_UID;
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
- public void setBackupServiceActive_callerRootUid_serviceCreated() {
+ public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() {
+ when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+ mTrampoline.initializeService();
TrampolineTestable.sCallingUid = Process.ROOT_UID;
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
- assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
}
@Test
- public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() {
+ public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() {
+ when(mUserInfoMock.isManagedProfile()).thenReturn(true);
+ mTrampoline.initializeService();
TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID;
try {
- mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
fail();
} catch (SecurityException expected) {
}
+ }
- assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
+ @Test
+ public void setBackupServiceActive_forNonSystemUserAndCallerWithoutBackupPermission_throws() {
+ doThrow(new SecurityException())
+ .when(mContextMock)
+ .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString());
+ mTrampoline.initializeService();
+
+ try {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void setBackupServiceActive_forNonSystemUserAndCallerWithoutUserPermission_throws() {
+ doThrow(new SecurityException())
+ .when(mContextMock)
+ .enforceCallingOrSelfPermission(
+ eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString());
+ mTrampoline.initializeService();
+
+ try {
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+ fail();
+ } catch (SecurityException expected) {
+ }
}
@Test
public void setBackupServiceActive_backupDisabled_ignored() {
TrampolineTestable.sBackupDisabled = true;
TrampolineTestable trampoline = new TrampolineTestable(mContextMock);
+ trampoline.initializeService();
trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
@@ -296,14 +347,8 @@ public class TrampolineTest {
}
@Test
- public void setBackupServiceActive_nonUserSystem_ignored() {
- mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
-
- assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
- }
-
- @Test
public void setBackupServiceActive_alreadyActive_ignored() {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
assertEquals(1, mTrampoline.getCreateServiceCallsCount());
@@ -315,6 +360,7 @@ public class TrampolineTest {
@Test
public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false);
@@ -323,6 +369,7 @@ public class TrampolineTest {
@Test
public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() {
+ mTrampoline.initializeService();
mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM));
@@ -349,6 +396,21 @@ public class TrampolineTest {
}
@Test
+ public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() {
+ mTrampoline.initializeService();
+ int otherUser = NON_USER_SYSTEM + 1;
+ File activateFile = new File(mTestDir, "activate-" + otherUser);
+ TrampolineTestable.sActivatedFiles.append(otherUser, activateFile);
+ mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true);
+
+ assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM));
+ assertFalse(mTrampoline.isBackupServiceActive(otherUser));
+ activateFile.delete();
+ }
+
+ @Test
public void dataChanged_calledBeforeInitialize_ignored() throws Exception {
mTrampoline.dataChanged(PACKAGE_NAME);
verifyNoMoreInteractions(mBackupManagerServiceMock);
@@ -1123,7 +1185,7 @@ public class TrampolineTest {
@Test
public void requestBackup_forwardedToCallingUserId() throws Exception {
TrampolineTestable.sCallingUserId = mUserId;
- when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES,
+ when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES,
mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456);
mTrampoline.initializeService();
@@ -1227,50 +1289,33 @@ public class TrampolineTest {
static int sCallingUserId = -1;
static int sCallingUid = -1;
static BackupManagerService sBackupManagerServiceMock = null;
+ static File sSuppressFile = null;
+ static SparseArray<File> sActivatedFiles = new SparseArray<>();
+ static UserManager sUserManagerMock = null;
private int mCreateServiceCallsCount = 0;
- private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>();
-
- private static class FakeFile extends File {
- private boolean mExists;
-
- FakeFile(String pathname) {
- super(pathname);
- }
-
- @Override
- public boolean exists() {
- return mExists;
- }
-
- @Override
- public boolean delete() {
- mExists = false;
- return true;
- }
-
- @Override
- public boolean createNewFile() throws IOException {
- mExists = true;
- return true;
- }
- }
TrampolineTestable(Context context) {
super(context);
}
@Override
+ protected UserManager getUserManager() {
+ return sUserManagerMock;
+ }
+
+ @Override
public boolean isBackupDisabled() {
return sBackupDisabled;
}
@Override
- public File getSuppressFileForUser(int userId) {
- if (mSuppressFiles.get(userId) == null) {
- FakeFile file = new FakeFile(Integer.toString(userId));
- mSuppressFiles.append(userId, file);
- }
- return mSuppressFiles.get(userId);
+ protected File getSuppressFileForSystemUser() {
+ return sSuppressFile;
+ }
+
+ @Override
+ protected File getActivatedFileForNonSystemUser(int userId) {
+ return sActivatedFiles.get(userId);
}
protected int binderGetCallingUserId() {
@@ -1289,11 +1334,6 @@ public class TrampolineTest {
}
@Override
- protected void createBackupSuppressFileForUser(int userId) throws IOException {
- getSuppressFileForUser(userId).createNewFile();
- }
-
- @Override
protected void postToHandler(Runnable runnable) {
runnable.run();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0813e6fa0252..535198b3dbfa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5239,6 +5239,45 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
}
+ public void testCrossProfileCalendarPackages_initiallyEmpty() {
+ setAsProfileOwner(admin1);
+ final Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+ }
+
+ public void testCrossProfileCalendarPackages_reopenDpms() {
+ setAsProfileOwner(admin1);
+ dpm.setCrossProfileCalendarPackages(admin1, null);
+ Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertTrue(packages == null);
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertTrue(packages == null);
+
+ dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet());
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+
+ final String dummyPackageName = "test";
+ final Set<String> testPackages = new ArraySet<String>(Arrays.asList(dummyPackageName));
+ dpm.setCrossProfileCalendarPackages(admin1, testPackages);
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+ }
+
+ private void assertCrossProfileCalendarPackagesEqual(Set<String> expected, Set<String> actual) {
+ assertTrue(expected != null);
+ assertTrue(actual != null);
+ assertTrue(expected.containsAll(actual));
+ assertTrue(actual.containsAll(expected));
+ }
+
private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
index e0ecd3ee24f0..0b01657868a8 100644
--- a/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ColorDisplayServiceTest.java
@@ -25,6 +25,7 @@ import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
import android.content.ContextWrapper;
+import android.hardware.display.ColorDisplayManager;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -45,7 +46,9 @@ import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -67,16 +70,23 @@ public class ColorDisplayServiceTest {
private MockTwilightManager mTwilightManager;
- private ColorDisplayController mColorDisplayController;
private ColorDisplayService mColorDisplayService;
+ private ColorDisplayController mColorDisplayController;
+ private ColorDisplayService.BinderService mBinderService;
+
+ @BeforeClass
+ public static void setDtm() {
+ final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
+ LocalServices.addService(DisplayTransformManager.class, dtm);
+ }
@Before
public void setUp() {
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mUserId = ActivityManager.getCurrentUser();
-
doReturn(mContext).when(mContext).getApplicationContext();
+ mUserId = ActivityManager.getCurrentUser();
+
final MockContentResolver cr = new MockContentResolver(mContext);
cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
doReturn(cr).when(mContext).getContentResolver();
@@ -84,23 +94,19 @@ public class ColorDisplayServiceTest {
final AlarmManager am = Mockito.mock(AlarmManager.class);
doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
- final DisplayTransformManager dtm = Mockito.mock(DisplayTransformManager.class);
- LocalServices.addService(DisplayTransformManager.class, dtm);
-
mTwilightManager = new MockTwilightManager();
LocalServices.addService(TwilightManager.class, mTwilightManager);
mColorDisplayController = new ColorDisplayController(mContext, mUserId);
mColorDisplayService = new ColorDisplayService(mContext);
+ mBinderService = mColorDisplayService.new BinderService();
}
@After
public void tearDown() {
- LocalServices.removeServiceForTest(DisplayTransformManager.class);
LocalServices.removeServiceForTest(TwilightManager.class);
mColorDisplayService = null;
- mColorDisplayController = null;
mTwilightManager = null;
@@ -108,6 +114,11 @@ public class ColorDisplayServiceTest {
mContext = null;
}
+ @AfterClass
+ public static void removeDtm() {
+ LocalServices.removeServiceForTest(DisplayTransformManager.class);
+ }
+
@Test
public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
@@ -907,15 +918,14 @@ public class ColorDisplayServiceTest {
}
setAccessibilityColorInversion(true);
- setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+ setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- assertAccessibilityTransformActivated(true /* activated */ );
- assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
- if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
- } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+ assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+ if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+ } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
}
}
@@ -926,15 +936,14 @@ public class ColorDisplayServiceTest {
}
setAccessibilityColorCorrection(true);
- setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+ setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- assertAccessibilityTransformActivated(true /* activated */ );
- assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
- if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
- } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+ assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+ if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+ } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
}
}
@@ -946,15 +955,14 @@ public class ColorDisplayServiceTest {
setAccessibilityColorCorrection(true);
setAccessibilityColorInversion(true);
- setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+ setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- assertAccessibilityTransformActivated(true /* activated */ );
- assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
- if (isColorModeValid(ColorDisplayController.COLOR_MODE_SATURATED)) {
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_SATURATED);
- } else if (isColorModeValid(ColorDisplayController.COLOR_MODE_AUTOMATIC)) {
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_AUTOMATIC);
+ assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+ if (isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+ } else if (isColorModeValid(ColorDisplayManager.COLOR_MODE_AUTOMATIC)) {
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_AUTOMATIC);
}
}
@@ -966,12 +974,11 @@ public class ColorDisplayServiceTest {
setAccessibilityColorCorrection(false);
setAccessibilityColorInversion(false);
- setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+ setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
startService();
- assertAccessibilityTransformActivated(false /* activated */ );
- assertUserColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
- assertActiveColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
+ assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+ assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
}
/**
@@ -981,7 +988,7 @@ public class ColorDisplayServiceTest {
* @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
*/
private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
- mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM);
+ mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
mColorDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset));
mColorDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset));
}
@@ -993,7 +1000,7 @@ public class ColorDisplayServiceTest {
* @param sunriseOffset the offset relative to now for sunrise (in minutes)
*/
private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
- mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_TWILIGHT);
+ mColorDisplayController.setAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT);
mTwilightManager.setTwilightState(
getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
}
@@ -1006,7 +1013,7 @@ public class ColorDisplayServiceTest {
* activated (in minutes)
*/
private void setActivated(boolean activated, int lastActivatedTimeOffset) {
- mColorDisplayController.setActivated(activated);
+ mBinderService.setNightDisplayActivated(activated);
Secure.putStringForUser(mContext.getContentResolver(),
Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
@@ -1036,10 +1043,10 @@ public class ColorDisplayServiceTest {
/**
* Configures color mode via ColorDisplayController.
*
- * @param mode the color mode to set
+ * @param colorMode the color mode to set
*/
- private void setColorMode(int mode) {
- mColorDisplayController.setColorMode(mode);
+ private void setColorMode(int colorMode) {
+ mColorDisplayController.setColorMode(colorMode);
}
/**
@@ -1081,23 +1088,12 @@ public class ColorDisplayServiceTest {
* @param activated the expected activated state of Night display
*/
private void assertActivated(boolean activated) {
- assertWithMessage("Invalid Night display activated state")
- .that(mColorDisplayController.isActivated())
+ assertWithMessage("Incorrect Night display activated state")
+ .that(mBinderService.isNightDisplayActivated())
.isEqualTo(activated);
}
/**
- * Convenience method for asserting that Accessibility color transform is detected.
- *
- * @param state {@code true} if any Accessibility transform should be activated
- */
- private void assertAccessibilityTransformActivated(boolean state) {
- assertWithMessage("Unexpected Accessibility color transform state")
- .that(mColorDisplayController.getAccessibilityTransformActivated())
- .isEqualTo(state);
- }
-
- /**
* Convenience method for asserting that the active color mode matches expectation.
*
* @param mode the expected active color mode.
diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
index 01199a36ff5b..0219f2201675 100644
--- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
@@ -17,15 +17,15 @@ package com.android.server.job;
import android.util.KeyValueListParser;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.job.JobSchedulerService.MaxJobCounts;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MaxJobCountsTest {
@@ -43,13 +43,14 @@ public class MaxJobCountsTest {
counts.parse(parser);
- Assert.assertEquals(expectedTotal, counts.getTotalMax());
+ Assert.assertEquals(expectedTotal, counts.getMaxTotal());
Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
Assert.assertEquals(expectedMinBg, counts.getMinBg());
}
@Test
public void test() {
+ // Tests with various combinations.
check("", /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
check("", /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
check("", /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
@@ -58,7 +59,11 @@ public class MaxJobCountsTest {
check("", /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
check("", /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
check("", /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
+ check("", /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
+ check("", /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
+ check("", /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
+ // Test for overriding with a setting string.
check("total=5,maxbg=4,minbg=3", /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
check("total=5", /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
check("maxbg=4", /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index bc1f7981258d..6845f15f6a28 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -43,6 +43,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.IUidObserver;
import android.app.Person;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -143,8 +144,10 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
switch (name) {
case Context.USER_SERVICE:
return mMockUserManager;
+ case Context.DEVICE_POLICY_SERVICE:
+ return mMockDevicePolicyManager;
}
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Couldn't find system service: " + name);
}
@Override
@@ -610,6 +613,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
protected PackageManager mMockPackageManager;
protected PackageManagerInternal mMockPackageManagerInternal;
protected UserManager mMockUserManager;
+ protected DevicePolicyManager mMockDevicePolicyManager;
protected UserManagerInternal mMockUserManagerInternal;
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -750,6 +754,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
mMockPackageManager = mock(PackageManager.class);
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockUserManager = mock(UserManager.class);
+ mMockDevicePolicyManager = mock(DevicePolicyManager.class);
mMockUserManagerInternal = mock(UserManagerInternal.class);
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 73e96134167a..742ae41ed9f0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,7 +173,8 @@ public class PackageInstallerSessionTest {
/* isReady */ staged ? true : false,
/* isFailed */ false,
/* isApplied */false,
- /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED);
+ /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED,
+ /* stagedSessionErrorMessage */ "some error");
}
private void dumpSession(PackageInstallerSession session) {
@@ -295,6 +296,8 @@ public class PackageInstallerSessionTest {
assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed());
assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady());
assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode());
+ assertEquals(expected.getStagedSessionErrorMessage(),
+ actual.getStagedSessionErrorMessage());
assertEquals(expected.isPrepared(), actual.isPrepared());
assertEquals(expected.isSealed(), actual.isSealed());
assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
new file mode 100644
index 000000000000..9f1cbcd7ec27
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.power;
+
+import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.attention.AttentionManagerInternal;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class AttentionDetectorTest extends AndroidTestCase {
+
+ private @Mock AttentionManagerInternal mAttentionManagerInternal;
+ private @Mock Runnable mOnUserAttention;
+ private TestableAttentionDetector mAttentionDetector;
+ private long mAttentionTimeout;
+ private long mNextDimming;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any()))
+ .thenReturn(true);
+ mAttentionDetector = new TestableAttentionDetector();
+ mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ mAttentionDetector.setAttentionServiceSupported(true);
+ mNextDimming = SystemClock.uptimeMillis() + 3000L;
+ }
+
+ @Test
+ public void testOnUserActivity_checksAttention() {
+ long when = registerAttention();
+ verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any());
+ assertThat(when).isLessThan(mNextDimming);
+ }
+
+ @Test
+ public void testOnUserActivity_doesntCheckIfNotSupported() {
+ mAttentionDetector.setAttentionServiceSupported(false);
+ long when = registerAttention();
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ assertThat(mNextDimming).isEqualTo(when);
+ }
+
+ @Test
+ public void onUserActivity_ignoresWhiteListedActivityTypes() {
+ for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) {
+ int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i);
+ if (result == -1) {
+ throw new AssertionError("User activity " + i + " isn't listed in"
+ + " AttentionDetector#onUserActivity. Please consider how this new activity"
+ + " type affects the attention service.");
+ }
+ }
+ }
+
+ @Test
+ public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() {
+ long now = SystemClock.uptimeMillis();
+ mNextDimming = now;
+ mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ mAttentionDetector.updateUserActivity(mNextDimming + 5000L);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testOnUserActivity_ignoresAfterMaximumExtension() {
+ long now = SystemClock.uptimeMillis();
+ mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ mAttentionDetector.updateUserActivity(now + 2000L);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testOnUserActivity_skipsIfAlreadyScheduled() {
+ registerAttention();
+ reset(mAttentionManagerInternal);
+ long when = mAttentionDetector.updateUserActivity(mNextDimming);
+ verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+ assertThat(when).isLessThan(mNextDimming);
+ }
+
+ @Test
+ public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() {
+ registerAttention();
+ mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+ verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt());
+ }
+
+ @Test
+ public void testCallbackOnSuccess_ignoresIfNoAttention() {
+ registerAttention();
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention, never()).run();
+ }
+
+ @Test
+ public void testCallbackOnSuccess_callsCallback() {
+ registerAttention();
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention).run();
+ }
+
+ @Test
+ public void testCallbackOnFailure_unregistersCurrentRequestCode() {
+ registerAttention();
+ mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_FAILURE_UNKNOWN);
+ mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+ AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+ verify(mOnUserAttention, never()).run();
+ }
+
+ private long registerAttention() {
+ mAttentionTimeout = 4000L;
+ mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+ return mAttentionDetector.updateUserActivity(mNextDimming);
+ }
+
+ private class TestableAttentionDetector extends AttentionDetector {
+
+ private boolean mAttentionServiceSupported;
+
+ TestableAttentionDetector() {
+ super(AttentionDetectorTest.this.mOnUserAttention, new Object());
+ mAttentionManager = mAttentionManagerInternal;
+ mMaximumExtensionMillis = 10000L;
+ }
+
+ void setAttentionServiceSupported(boolean supported) {
+ mAttentionServiceSupported = supported;
+ }
+
+ @Override
+ public boolean isAttentionServiceSupported() {
+ return mAttentionServiceSupported;
+ }
+
+ @Override
+ public long getAttentionTimeout() {
+ return mAttentionTimeout;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index b348aeef802e..5d69bbdcf0c7 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -18,12 +18,15 @@ package com.android.server.usage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.UserHandle;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +67,7 @@ public class AppTimeLimitControllerTests {
private static final long TIME_30_MIN = 30 * 60_000L;
private static final long TIME_10_MIN = 10 * 60_000L;
- private static final long TIME_1_MIN = 10 * 60_000L;
+ private static final long TIME_1_MIN = 1 * 60_000L;
private static final long MAX_OBSERVER_PER_UID = 10;
private static final long MIN_TIME_LIMIT = 4_000L;
@@ -128,6 +131,11 @@ public class AppTimeLimitControllerTests {
}
@Override
+ protected long getAppUsageLimitObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ @Override
protected long getMinTimeLimit() {
return MIN_TIME_LIMIT;
}
@@ -164,6 +172,16 @@ public class AppTimeLimitControllerTests {
assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
}
+ /** Verify app usage limit observer is added */
+ @Test
+ public void testAppUsageLimitObserver_AddObserver() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Verify app usage observer is removed */
@Test
public void testAppUsageObserver_RemoveObserver() {
@@ -182,6 +200,15 @@ public class AppTimeLimitControllerTests {
assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify app usage limit observer is removed */
+ @Test
+ public void testAppUsageLimitObserver_RemoveObserver() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Verify nothing happens when a nonexistent app usage observer is removed */
@Test
public void testAppUsageObserver_RemoveMissingObserver() {
@@ -218,6 +245,24 @@ public class AppTimeLimitControllerTests {
assertFalse("Observer should not exist", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify nothing happens when a nonexistent app usage limit observer is removed */
+ @Test
+ public void testAppUsageLimitObserver_RemoveMissingObserver() {
+ assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+ try {
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ sw.write("Hit exception trying to remove nonexistent observer:\n");
+ sw.write(e.toString());
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ sw.write("\nTest Failed!");
+ fail(sw.toString());
+ }
+ assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Re-adding an observer should result in only one copy */
@Test
public void testAppUsageObserver_ObserverReAdd() {
@@ -242,22 +287,39 @@ public class AppTimeLimitControllerTests {
assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Re-adding an observer should result in only one copy */
+ @Test
+ public void testAppUsageLimitObserver_ObserverReAdd() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ assertTrue("Observer wasn't added",
+ getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Different type observers can be registered to the same observerId value */
@Test
public void testAllObservers_ExclusiveObserverIds() {
addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
OBS_ID1);
AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
OBS_ID1);
+ AppTimeLimitController.UsageGroup appUsageLimitGroup = getAppUsageLimitObserver(
+ UID, OBS_ID1);
// Verify data still intact
assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
+ assertEquals(TIME_10_MIN, appUsageLimitGroup.getTimeLimitMs());
}
/** Verify that usage across different apps within a group are added up */
@@ -299,7 +361,7 @@ public class AppTimeLimitControllerTests {
@Test
public void testUsageSessionObserver_Accumulation() throws Exception {
setTime(0L);
- addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_10_MIN);
startUsage(PKG_SOC1);
// Add 10 mins
setTime(TIME_10_MIN);
@@ -330,6 +392,41 @@ public class AppTimeLimitControllerTests {
assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
+ /** Verify that usage across different apps within a group are added up */
+ @Test
+ public void testAppUsageLimitObserver_Accumulation() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+
+ AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN * 2);
+ stopUsage(PKG_SOC1);
+
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN, timeRemaining);
+
+ setTime(TIME_30_MIN);
+
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ // Add a different package in the group
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify that time limit does not get triggered due to a different app */
@Test
public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
@@ -355,6 +452,18 @@ public class AppTimeLimitControllerTests {
}
+ /** Verify that time limit does not get triggered due to a different app */
+ @Test
+ public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(6_000L);
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify the timeout message is delivered at the right time */
@Test
public void testAppUsageObserver_Timeout() throws Exception {
@@ -385,6 +494,19 @@ public class AppTimeLimitControllerTests {
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify the timeout message is delivered at the right time */
+ @Test
+ public void testAppUsageLimitObserver_Timeout() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** If an app was already running, make sure it is partially counted towards the time limit */
@Test
public void testAppUsageObserver_AlreadyRunning() throws Exception {
@@ -423,6 +545,25 @@ public class AppTimeLimitControllerTests {
assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
}
+ /** If an app was already running, make sure it is partially counted towards the time limit */
+ @Test
+ public void testAppUsageLimitObserver_AlreadyRunning() throws Exception {
+ setTime(TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ startUsage(PKG_GAME2);
+ setTime(TIME_30_MIN + TIME_30_MIN);
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID2));
+ }
+
/** If watched app is already running, verify the timeout callback happens at the right time */
@Test
public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
@@ -464,6 +605,24 @@ public class AppTimeLimitControllerTests {
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** If watched app is already running, verify the timeout callback happens at the right time */
+ @Test
+ public void testAppUsageLimitObserver_AlreadyRunningTimeout() throws Exception {
+ setTime(0);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ // 10 second time limit
+ addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L);
+ setTime(TIME_10_MIN + 5_000L);
+ // Shouldn't call back in 6 seconds
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(TIME_10_MIN + 10_000L);
+ // Should call back by 11 seconds (6 earlier + 5 now)
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/**
* Verify that App Time Limit Controller will limit the number of observerIds for app usage
* observers
@@ -525,6 +684,37 @@ public class AppTimeLimitControllerTests {
assertTrue("Should have caused an IllegalStateException", receivedException);
}
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+ * limit observers
+ */
+ @Test
+ public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception {
+ boolean receivedException = false;
+ int ANOTHER_UID = UID + 1;
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+ // Readding an observer should not cause an IllegalStateException
+ addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ // Adding an observer for a different uid shouldn't cause an IllegalStateException
+ mController.addAppUsageLimitObserver(
+ ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+ try {
+ addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+ } catch (IllegalStateException ise) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalStateException", receivedException);
+ }
+
/** Verify that addAppUsageObserver minimum time limit is one minute */
@Test
public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
@@ -553,6 +743,20 @@ public class AppTimeLimitControllerTests {
assertTrue("Should have caused an IllegalArgumentException", receivedException);
}
+ /** Verify that addAppUsageLimitObserver minimum time limit is one minute */
+ @Test
+ public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception {
+ boolean receivedException = false;
+ // adding an observer with a one minute time limit should not cause an exception
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+ try {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+ } catch (IllegalArgumentException iae) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalArgumentException", receivedException);
+ }
+
/** Verify that concurrent usage from multiple apps in the same group will counted correctly */
@Test
public void testAppUsageObserver_ConcurrentUsage() throws Exception {
@@ -599,6 +803,29 @@ public class AppTimeLimitControllerTests {
assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify that a session will continue if usage starts again within the session threshold */
@Test
public void testUsageSessionObserver_ContinueSession() throws Exception {
@@ -737,6 +964,97 @@ public class AppTimeLimitControllerTests {
assertFalse(hasAppUsageObserver(UID, OBS_ID1));
}
+ /** Verify app usage limit observer added correctly reports it being a group limit */
+ @Test
+ public void testAppUsageLimitObserver_IsGroupLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertTrue("Observer didn't correctly report being a group limit",
+ group.isGroupLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports it being not a group limit */
+ @Test
+ public void testAppUsageLimitObserver_IsNotGroupLimit() {
+ addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertFalse("Observer didn't correctly report not being a group limit",
+ group.isGroupLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports its total usage limit */
+ @Test
+ public void testAppUsageLimitObserver_GetTotalUsageLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertEquals("Observer didn't correctly report total usage limit",
+ TIME_30_MIN, group.getTotaUsageLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports its total usage limit */
+ @Test
+ public void testAppUsageLimitObserver_GetUsageRemaining() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertEquals("Observer didn't correctly report total usage limit",
+ TIME_10_MIN * 2, group.getUsageRemaining());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ TIME_10_MIN, group.getTotalUsageLimit());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimitUsed() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2 + TIME_1_MIN);
+ stopUsage(PKG_GAME1);
+ // PKG_GAME1 is only in GROUP1 but since we're querying for PCK_SOC1 which is
+ // in both groups, GROUP1 should be returned since it has a smaller time remaining
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ TIME_1_MIN * 9, group.getUsageRemaining());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+ // GROUP_SOC should be returned since it should be completely used up (0ms remaining)
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ 0L, group.getUsageRemaining());
+ }
+
private void startUsage(String packageName) {
mController.noteUsageStart(packageName, USER_ID);
}
@@ -759,6 +1077,10 @@ public class AppTimeLimitControllerTests {
null, null, USER_ID);
}
+ private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) {
+ mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+ }
+
/** Is there still an app usage observer by that id */
private boolean hasAppUsageObserver(int uid, int observerId) {
return mController.getAppUsageGroup(uid, observerId) != null;
@@ -769,6 +1091,20 @@ public class AppTimeLimitControllerTests {
return mController.getSessionUsageGroup(uid, observerId) != null;
}
+ /** Is there still an app usage limit observer by that id */
+ private boolean hasAppUsageLimitObserver(int uid, int observerId) {
+ return mController.getAppUsageLimitGroup(uid, observerId) != null;
+ }
+
+ private AppTimeLimitController.AppUsageLimitGroup getAppUsageLimitObserver(
+ int uid, int observerId) {
+ return mController.getAppUsageLimitGroup(uid, observerId);
+ }
+
+ private UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(String packageName) {
+ return mController.getAppUsageLimit(packageName, UserHandle.of(USER_ID));
+ }
+
private void setTime(long time) {
mUptimeMillis = time;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 62229235a026..9c6ab0ab9aa9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3410,6 +3410,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() {
+ // Post 2 notifications from 2 packages
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgB);
+
+ // on broadcast, hide one of the packages
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"});
+ ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
+ assertEquals(1, captorHide.getValue().size());
+ assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+
+ // on broadcast, unhide the package
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"});
+ ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
+ assertEquals(1, captorUnhide.getValue().size());
+ assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+ }
+
+ @Test
+ public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() {
+ // Post 2 notifications from 2 packages
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ mService.addNotification(pkgB);
+
+ // on broadcast, hide one of the packages
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"});
+ ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture());
+ assertEquals(2, captorHide.getValue().size());
+ assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+ assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName());
+
+ // on broadcast, unhide the package
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"});
+ ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture());
+ assertEquals(2, captorUnhide.getValue().size());
+ assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+ assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName());
+ }
+
+ @Test
+ public void testNoNotificationsHiddenOnDistractingPackageBroadcast() {
+ // post notification from this package
+ final NotificationRecord notif1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ mService.addNotification(notif1);
+
+ // on broadcast, nothing is hidden since no notifications are of package "test_package"
+ mService.simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"});
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
+ assertEquals(0, captor.getValue().size());
+ }
+
+ @Test
public void testCanUseManagedServicesLowRamNoWatchNullPkg() {
when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
when(mActivityManager.isLowRamDevice()).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8be63fc43adb..319ffed3778c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,6 +31,7 @@ import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
@@ -75,6 +76,9 @@ public class ActivityRecordTests extends ActivityTestsBase {
mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
mTask = mStack.getChildAt(0);
mActivity = mTask.getTopActivity();
+
+ doReturn(false).when(mService).isBooting();
+ doReturn(true).when(mService).isBooted();
}
@Test
@@ -117,22 +121,23 @@ public class ActivityRecordTests extends ActivityTestsBase {
mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
- // The activity is in the focused stack so it should not move to paused.
+ // The activity is in the focused stack so it should be resumed.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
- assertTrue(mActivity.isState(STOPPED));
+ assertTrue(mActivity.isState(RESUMED));
assertFalse(pauseFound.value);
- // Clear focused stack
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
- when(display.getFocusedStack()).thenReturn(null);
+ // Make the activity non focusable
+ mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+ doReturn(false).when(mActivity).isFocusable();
- // In the unfocused stack, the activity should move to paused.
+ // If the activity is not focusable, it should move to paused.
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
assertTrue(mActivity.isState(PAUSING));
assertTrue(pauseFound.value);
// Make sure that the state does not change for current non-stopping states.
mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
+ doReturn(true).when(mActivity).isFocusable();
mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index a381023590c3..056568a0de64 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -549,8 +549,7 @@ public class ActivityStarterTests extends ActivityTestsBase {
verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(),
eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(),
eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(),
- eq(ActivityBuilder.getDefaultComponent().getPackageName()), anyInt(), anyBoolean(),
- any(), eq(false));
+ any(), anyInt(), anyBoolean(), any(), eq(false));
}
/**
@@ -599,6 +598,10 @@ public class ActivityStarterTests extends ActivityTestsBase {
Process.SYSTEM_UID, false, PROCESS_STATE_TOP + 1,
UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
false, false, false);
+ runAndVerifyBackgroundActivityStartsSubtest("disallowed_nfcUid_notAborted", false,
+ Process.NFC_UID, false, PROCESS_STATE_TOP + 1,
+ UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1,
+ false, false, false);
runAndVerifyBackgroundActivityStartsSubtest(
"disallowed_callingUidHasVisibleWindow_notAborted", false,
UNIMPORTANT_UID, true, PROCESS_STATE_TOP + 1,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 68df87e3e27d..ea8f33f0c630 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,6 +55,7 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.Process;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
@@ -425,6 +426,7 @@ class ActivityTestsBase {
doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
// allow background activity starts by default
doReturn(true).when(this).isBackgroundActivityStartsEnabled();
+ doNothing().when(this).updateCpuStats();
}
void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
@@ -580,6 +582,8 @@ class ActivityTestsBase {
doNothing().when(this).acquireLaunchWakelock();
doReturn(mKeyguardController).when(this).getKeyguardController();
+ mLaunchingActivity = mock(PowerManager.WakeLock.class);
+
initialize();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
new file mode 100644
index 000000000000..19ace3c0336c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.IBinder;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.view.RemoteAnimationTarget;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for change transitions
+ *
+ * Build/Install/Run:
+ * atest WmTests:AppChangeTransitionTests
+ */
+@FlakyTest(detail = "Promote when shown to be stable.")
+@SmallTest
+public class AppChangeTransitionTests extends WindowTestsBase {
+
+ private TaskStack mStack;
+ private Task mTask;
+ private WindowTestUtils.TestAppWindowToken mToken;
+
+ @Before
+ public void setUp() throws Exception {
+ mStack = createTaskStackOnDisplay(mDisplayContent);
+ mTask = createTaskInStack(mStack, 0 /* userId */);
+ mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+ mToken.mSkipOnParentSet = false;
+
+ mTask.addChild(mToken, 0);
+ }
+
+ class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
+ @Override
+ public void onAnimationStart(RemoteAnimationTarget[] apps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ for (RemoteAnimationTarget target : apps) {
+ assertNotNull(target.startBounds);
+ }
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (Exception e) {
+ throw new RuntimeException("Something went wrong");
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ }
+
+ @Test
+ public void testModeChangeRemoteAnimatorNoSnapshot() {
+ RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ RemoteAnimationAdapter adapter =
+ new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false);
+ definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter);
+ mDisplayContent.registerRemoteAnimations(definition);
+
+ mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(1, mDisplayContent.mChangingApps.size());
+ assertNull(mToken.getThumbnail());
+
+ waitUntilHandlersIdle();
+ mToken.removeImmediately();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 374078625f4a..a498a1a9172a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -94,9 +94,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
topBar.getFrameLw().set(0, 0, 500, 100);
mProvider.setWindow(topBar, null);
- mProvider.updateControlForTarget(target);
+ mProvider.updateControlForTarget(target, false /* force */);
assertNotNull(mProvider.getControl());
- mProvider.updateControlForTarget(null);
+ mProvider.updateControlForTarget(null, false /* force */);
assertNull(mProvider.getControl());
}
@@ -106,7 +106,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
topBar.getFrameLw().set(0, 0, 500, 100);
mProvider.setWindow(topBar, null);
- mProvider.updateControlForTarget(target);
+ mProvider.updateControlForTarget(target, false /* force */);
InsetsState state = new InsetsState();
state.getSource(TYPE_TOP_BAR).setVisible(false);
mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 9478be90b5c3..b867799c16cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -77,7 +77,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
MockitoAnnotations.initMocks(this);
when(mMockRunner.asBinder()).thenReturn(new Binder());
- mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50);
+ mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
mAdapter.setCallingPid(123);
mWm.mH.runWithScissors(() -> mHandler = new TestHandler(null, mClock), 0);
mController = new RemoteAnimationController(mWm, mAdapter, mHandler);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
new file mode 100644
index 000000000000..a7c84a1c28b4
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wm;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+/**
+ * Tests for the {@link WindowProcessController} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:WindowProcessControllerTests
+ */
+@Presubmit
+public class WindowProcessControllerTests extends ActivityTestsBase {
+
+ @Test
+ public void testDisplayConfigurationListener() {
+ final WindowProcessController wpc = new WindowProcessController(
+ mService, mock(ApplicationInfo.class), null, 0, -1, null, null);
+ //By default, the process should not listen to any display.
+ assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+
+ // Register to display 1 as a listener.
+ TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer();
+ wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
+ assertTrue(testActivityDisplay1.containsListener(wpc));
+ assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId());
+
+ // Move to display 2.
+ TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer();
+ wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2);
+ assertFalse(testActivityDisplay1.containsListener(wpc));
+ assertTrue(testActivityDisplay2.containsListener(wpc));
+ assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId());
+
+ // Null ActivityDisplay will not change anything.
+ wpc.registerDisplayConfigurationListenerLocked(null);
+ assertTrue(testActivityDisplay2.containsListener(wpc));
+ assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId());
+
+ // Unregister listener will remove the wpc from registered displays.
+ wpc.unregisterDisplayConfigurationListenerLocked();
+ assertFalse(testActivityDisplay1.containsListener(wpc));
+ assertFalse(testActivityDisplay2.containsListener(wpc));
+ assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+
+ // Unregistration still work even if the display was removed.
+ wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
+ assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId());
+ mRootActivityContainer.removeChild(testActivityDisplay1);
+ wpc.unregisterDisplayConfigurationListenerLocked();
+ assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+ }
+
+ private TestActivityDisplay createTestActivityDisplayInContainer() {
+ final TestActivityDisplay testActivityDisplay = createNewActivityDisplay();
+ mRootActivityContainer.addChild(testActivityDisplay, POSITION_TOP);
+ return testActivityDisplay;
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 44e998b7e62a..2263cf3a02e0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -152,6 +152,7 @@ public class WindowTestUtils {
public static class TestAppWindowToken extends AppWindowToken {
boolean mOnTop = false;
private Transaction mPendingTransactionOverride;
+ boolean mSkipOnParentSet = true;
private TestAppWindowToken(DisplayContent dc) {
super(dc.mWmService, new IApplicationToken.Stub() {
@@ -200,7 +201,9 @@ public class WindowTestUtils {
@Override
void onParentSet() {
- // Do nothing.
+ if (!mSkipOnParentSet) {
+ super.onParentSet();
+ }
}
@Override
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 2ed11fe92e15..fa472e2575f0 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -18,11 +18,14 @@ package com.android.server.usage;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -163,6 +166,9 @@ public class AppTimeLimitController {
/** Map of observerId to details of the time limit group */
SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
+ /** Map of observerId to details of the app usage limit group */
+ SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>();
+
private ObserverAppData(int uid) {
this.uid = uid;
}
@@ -177,6 +183,10 @@ public class AppTimeLimitController {
sessionUsageGroups.remove(observerId);
}
+ @GuardedBy("mLock")
+ void removeAppUsageLimitGroup(int observerId) {
+ appUsageLimitGroups.remove(observerId);
+ }
@GuardedBy("mLock")
void dump(PrintWriter pw) {
@@ -194,6 +204,12 @@ public class AppTimeLimitController {
sessionUsageGroups.valueAt(i).dump(pw);
pw.println();
}
+ pw.println(" App Usage Limit Groups:");
+ final int nAppUsageLimitGroups = appUsageLimitGroups.size();
+ for (int i = 0; i < nAppUsageLimitGroups; i++) {
+ appUsageLimitGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
}
}
@@ -493,6 +509,54 @@ public class AppTimeLimitController {
}
}
+ class AppUsageLimitGroup extends UsageGroup {
+ private boolean mGroupLimit;
+
+ public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ mGroupLimit = observed.length > 1;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeAppUsageLimitGroup(mObserverId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+
+ @GuardedBy("mLock")
+ long getTotaUsageLimit() {
+ return mTimeLimitMs;
+ }
+
+ @GuardedBy("mLock")
+ long getUsageRemaining() {
+ // If there is currently an active session, account for its usage
+ if (mActives > 0) {
+ return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs);
+ } else {
+ return mTimeLimitMs - mUsageTimeMs;
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.print(" groupLimit=");
+ pw.print(mGroupLimit);
+ }
+ }
+
private class MyHandler extends Handler {
static final int MSG_CHECK_TIMEOUT = 1;
@@ -553,6 +617,12 @@ public class AppTimeLimitController {
/** Overrideable for testing purposes */
@VisibleForTesting
+ protected long getAppUsageLimitObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ /** Overrideable for testing purposes */
+ @VisibleForTesting
protected long getMinTimeLimit() {
return ONE_MINUTE;
}
@@ -572,6 +642,61 @@ public class AppTimeLimitController {
}
}
+ @VisibleForTesting
+ AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get(
+ observerId);
+ }
+ }
+
+ /**
+ * Returns an object describing the app usage limit for the given package which was set via
+ * {@link #addAppUsageLimitObserver).
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ */
+ public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(
+ String packageName, UserHandle user) {
+ synchronized (mLock) {
+ final UserData userData = getOrCreateUserDataLocked(user.getIdentifier());
+ if (userData == null) {
+ return null;
+ }
+
+ final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName);
+ if (usageGroups == null || usageGroups.isEmpty()) {
+ return null;
+ }
+
+ final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>();
+ for (int i = 0; i < usageGroups.size(); i++) {
+ if (usageGroups.get(i) instanceof AppUsageLimitGroup) {
+ final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i);
+ for (int j = 0; j < group.mObserved.length; j++) {
+ if (group.mObserved[j].equals(packageName)) {
+ usageLimitGroups.add(group);
+ break;
+ }
+ }
+ }
+ }
+ if (usageLimitGroups.isEmpty()) {
+ return null;
+ }
+
+ AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0);
+ for (int i = 1; i < usageLimitGroups.size(); i++) {
+ final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i);
+ if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) {
+ smallestGroup = otherGroup;
+ }
+ }
+ return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(),
+ smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
+ }
+ }
+
/** Returns an existing UserData object for the given userId, or creates one */
@GuardedBy("mLock")
private UserData getOrCreateUserDataLocked(int userId) {
@@ -726,6 +851,61 @@ public class AppTimeLimitController {
}
/**
+ * Registers an app usage limit observer with the given details.
+ * Existing app usage limit observer with the same observerId will be removed.
+ */
+ public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
+ if (timeLimit < getMinTimeLimit()) {
+ throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+ }
+ synchronized (mLock) {
+ UserData user = getOrCreateUserDataLocked(userId);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ group.remove();
+ }
+
+ final int observerIdCount = observerApp.appUsageLimitGroups.size();
+ if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) {
+ throw new IllegalStateException(
+ "Too many app usage observers added by uid " + requestingUid);
+ }
+ group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
+ callbackIntent);
+ observerApp.appUsageLimitGroups.append(observerId, group);
+
+ if (DEBUG) {
+ Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
+ }
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
+ }
+ }
+
+ /**
+ * Remove a registered observer by observerId and calling uid.
+ *
+ * @param requestingUid The calling uid
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
+ */
+ public void removeAppUsageLimitObserver(int requestingUid, int observerId,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ group.remove();
+ }
+ }
+ }
+
+ /**
* Called when an entity becomes active.
*
* @param name The entity that became active
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6ad698b39763..85939d498755 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -855,6 +855,22 @@ public class UsageStatsService extends SystemService implements
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean hasPermissions(String callingPackage, String... permissions) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SYSTEM_UID) {
+ // Caller is the system, so proceed.
+ return true;
+ }
+
+ boolean hasPermissions = true;
+ final Context context = getContext();
+ for (int i = 0; i < permissions.length; i++) {
+ hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i])
+ == PackageManager.PERMISSION_GRANTED);
+ }
+ return hasPermissions;
+ }
+
private void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallingUidSystem()) {
return;
@@ -1346,6 +1362,51 @@ public class UsageStatsService extends SystemService implements
}
@Override
+ public void registerAppUsageLimitObserver(int observerId, String[] packages,
+ long timeLimitMs, PendingIntent callbackIntent, String callingPackage) {
+ if (!hasPermissions(callingPackage,
+ Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+ throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ + "OBSERVE_APP_USAGE permissions");
+ }
+
+ if (packages == null || packages.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one package");
+ }
+ if (callbackIntent == null) {
+ throw new NullPointerException("callbackIntent can't be null");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
+ packages, timeLimitMs, callbackIntent, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
+ if (!hasPermissions(callingPackage,
+ Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+ throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ + "OBSERVE_APP_USAGE permissions");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.unregisterAppUsageLimitObserver(
+ callingUid, observerId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void reportUsageStart(IBinder activity, String token, String callingPackage) {
reportPastUsageStart(activity, token, 0, callingPackage);
}
@@ -1447,6 +1508,16 @@ public class UsageStatsService extends SystemService implements
mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
}
+ void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
+ long timeLimitMs, PendingIntent callbackIntent, int userId) {
+ mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs,
+ callbackIntent, userId);
+ }
+
+ void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
+ mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId);
+ }
+
/**
* This local service implementation is primarily used by ActivityManagerService.
* ActivityManagerService will call these methods holding the 'am' lock, which means we
@@ -1652,5 +1723,10 @@ public class UsageStatsService extends SystemService implements
public void reportExemptedSyncStart(String packageName, int userId) {
mAppStandby.postReportExemptedSyncStart(packageName, userId);
}
+
+ @Override
+ public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) {
+ return mAppTimeLimit.getAppUsageLimit(packageName, user);
+ }
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index d0b7a5e87f99..50e4faab76e3 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -61,6 +61,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.service.usb.UsbPortInfoProto;
import android.service.usb.UsbPortManagerProto;
+import android.service.usb.UsbServiceProto;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -74,7 +75,6 @@ import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.NoSuchElementException;
/**
@@ -141,7 +141,11 @@ public class UsbPortManager {
// Maintains the current connected status of the port.
// Uploads logs only when the connection status is changes.
- private final HashMap<String, Boolean> mConnected = new HashMap<>();
+ private final ArrayMap<String, Boolean> mConnected = new ArrayMap<>();
+
+ // Maintains the USB contaminant status that was previously logged.
+ // Logs get uploaded only when contaminant presence status changes.
+ private final ArrayMap<String, Integer> mContaminantStatus = new ArrayMap<>();
private NotificationManager mNotificationManager;
@@ -959,6 +963,24 @@ public class UsbPortManager {
updateContaminantNotification();
}
+ // Constants have to be converted between USB HAL V1.2 ContaminantDetectionStatus
+ // to usb.proto as proto guidelines recommends 0 to be UNKNOWN/UNSUPPORTTED
+ // whereas HAL policy is against a loosely defined constant.
+ private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) {
+ switch (contaminantDetectionStatus) {
+ case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED:
+ return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED;
+ case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED:
+ return UsbServiceProto.CONTAMINANT_STATUS_DISABLED;
+ case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED:
+ return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED;
+ case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED:
+ return UsbServiceProto.CONTAMINANT_STATUS_DETECTED;
+ default:
+ return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN;
+ }
+ }
+
private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);
intent.addFlags(
@@ -973,6 +995,33 @@ public class UsbPortManager {
Manifest.permission.MANAGE_USB));
// Log to statsd
+
+ // Port is removed
+ if (portInfo.mUsbPortStatus == null) {
+ if (mConnected.containsKey(portInfo.mUsbPort.getId())) {
+ //Previous logged a connected. Set it to disconnected.
+ if (mConnected.get(portInfo.mUsbPort.getId())) {
+ StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED,
+ StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
+ portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
+ }
+ mConnected.remove(portInfo.mUsbPort.getId());
+ }
+
+ if (mContaminantStatus.containsKey(portInfo.mUsbPort.getId())) {
+ //Previous logged a contaminant detected. Set it to not detected.
+ if ((mContaminantStatus.get(portInfo.mUsbPort.getId())
+ == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) {
+ StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
+ portInfo.mUsbPort.getId(),
+ convertContaminantDetectionStatusToProto(
+ UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED));
+ }
+ mContaminantStatus.remove(portInfo.mUsbPort.getId());
+ }
+ return;
+ }
+
if (!mConnected.containsKey(portInfo.mUsbPort.getId())
|| (mConnected.get(portInfo.mUsbPort.getId())
!= portInfo.mUsbPortStatus.isConnected())) {
@@ -983,6 +1032,17 @@ public class UsbPortManager {
StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
}
+
+ if (!mContaminantStatus.containsKey(portInfo.mUsbPort.getId())
+ || (mContaminantStatus.get(portInfo.mUsbPort.getId())
+ != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) {
+ mContaminantStatus.put(portInfo.mUsbPort.getId(),
+ portInfo.mUsbPortStatus.getContaminantDetectionStatus());
+ StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
+ portInfo.mUsbPort.getId(),
+ convertContaminantDetectionStatusToProto(
+ portInfo.mUsbPortStatus.getContaminantDetectionStatus()));
+ }
}
private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 8c82cc835ed9..697469a3c680 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -939,11 +939,7 @@ public class SoundTriggerService extends SystemService {
runOrAddOperation(new Operation(
// always execute:
() -> {
- // Don't remove the callback if multiple triggers are allowed or
- // if this event was triggered by a getModelState request
- if (!mRecognitionConfig.allowMultipleTriggers
- && event.status
- != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ if (!mRecognitionConfig.allowMultipleTriggers) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index bb01f041c5af..718f2d379883 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -17,13 +17,18 @@
package com.android.server.voiceinteraction;
import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -78,6 +83,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* SystemService that publishes an IVoiceInteractionManagerService.
@@ -200,6 +206,7 @@ public class VoiceInteractionManagerService extends SystemService {
VoiceInteractionManagerServiceStub() {
mEnableService = shouldEnableService(mContext);
+ new RoleObserver(mContext.getMainExecutor());
}
// TODO: VI Make sure the caller is the current user or profile
@@ -1268,6 +1275,106 @@ public class VoiceInteractionManagerService extends SystemService {
getActiveServiceComponentName());
}
+ class RoleObserver implements OnRoleHoldersChangedListener {
+ private PackageManager mPm = mContext.getPackageManager();
+ private RoleManager mRm = mContext.getSystemService(RoleManager.class);
+
+ RoleObserver(@NonNull @CallbackExecutor Executor executor) {
+ mRm.addOnRoleHoldersChangedListenerAsUser(executor, this, UserHandle.ALL);
+ }
+
+ private @NonNull String getDefaultRecognizer(@NonNull UserHandle user) {
+ ResolveInfo resolveInfo = mPm.resolveServiceAsUser(
+ new Intent(RecognitionService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA, user.getIdentifier());
+
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Log.w(TAG, "Unable to resolve default voice recognition service.");
+ return "";
+ }
+
+ return new ComponentName(resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name).flattenToShortString();
+ }
+
+ /**
+ * Convert the assistant-role holder into settings. The rest of the system uses the
+ * settings.
+ *
+ * @param roleName the name of the role whose holders are changed
+ * @param user the user for this role holder change
+ */
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ if (!roleName.equals(RoleManager.ROLE_ASSISTANT)) {
+ return;
+ }
+
+ List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+
+ if (roleHolders.isEmpty()) {
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ASSISTANT, "");
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE, getDefaultRecognizer(user));
+ } else {
+ // Assistant is singleton role
+ String pkg = roleHolders.get(0);
+
+ // Try to set role holder as VoiceInteractionService
+ List<ResolveInfo> services = mPm.queryIntentServicesAsUser(
+ new Intent(VoiceInteractionService.SERVICE_INTERFACE).setPackage(pkg),
+ PackageManager.GET_META_DATA, user.getIdentifier());
+
+ for (ResolveInfo resolveInfo : services) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+
+ VoiceInteractionServiceInfo voiceInteractionServiceInfo =
+ new VoiceInteractionServiceInfo(mPm, serviceInfo);
+ if (!voiceInteractionServiceInfo.getSupportsAssist()) {
+ continue;
+ }
+
+ String serviceComponentName = serviceInfo.getComponentName()
+ .flattenToShortString();
+
+ String serviceRecognizerName = new ComponentName(pkg,
+ voiceInteractionServiceInfo.getRecognitionService())
+ .flattenToShortString();
+
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ASSISTANT, serviceComponentName);
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, serviceComponentName);
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE, serviceRecognizerName);
+
+ return;
+ }
+
+ // If no service could be found try to set assist activity
+ final List<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser(
+ new Intent(Intent.ACTION_ASSIST).setPackage(pkg),
+ PackageManager.MATCH_DEFAULT_ONLY, user.getIdentifier());
+
+ for (ResolveInfo resolveInfo : activities) {
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ASSISTANT,
+ activityInfo.getComponentName().flattenToShortString());
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_INTERACTION_SERVICE, "");
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE,
+ getDefaultRecognizer(user));
+ }
+ }
+ }
+ }
+
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
index c2e4581285fd..acf994610182 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -24,9 +24,8 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
-// TODO: fix this. either move this class into system server or add a dependency on
-// these wm classes to libiorap-java and libiorap-java-tests (somehow).
import com.android.server.wm.ActivityMetricsLaunchObserver;
import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
@@ -113,12 +112,12 @@ public abstract class AppLaunchEvent implements Parcelable {
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
- intent.writeToParcel(p, flags);
+ IntentProtoParcelable.write(p, intent, flags);
}
IntentStarted(Parcel p) {
super(p);
- intent = Intent.CREATOR.createFromParcel(p);
+ intent = IntentProtoParcelable.create(p);
}
}
@@ -232,8 +231,7 @@ public abstract class AppLaunchEvent implements Parcelable {
}
public static class ActivityLaunchCancelled extends AppLaunchEvent {
- public final @Nullable
- @ActivityRecordProto byte[] activityRecordSnapshot;
+ public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot;
public ActivityLaunchCancelled(@SequenceId long sequenceId,
@Nullable @ActivityRecordProto byte[] snapshot) {
@@ -352,7 +350,6 @@ public abstract class AppLaunchEvent implements Parcelable {
ActivityLaunchCancelled.class,
};
- // TODO: move to @ActivityRecordProto byte[] once we have unit tests.
public static class ActivityRecordProtoParcelable {
public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
int flags) {
@@ -365,4 +362,31 @@ public abstract class AppLaunchEvent implements Parcelable {
return data;
}
}
+
+ public static class IntentProtoParcelable {
+ private static final int INTENT_PROTO_CHUNK_SIZE = 1024;
+
+ public static void write(Parcel p, @NonNull Intent intent, int flags) {
+ // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
+ // so create a new one every time.
+ final ProtoOutputStream protoOutputStream =
+ new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE);
+ // Write this data out as the top-most IntentProto (i.e. it is not a sub-object).
+ intent.writeToProto(protoOutputStream);
+ final byte[] bytes = protoOutputStream.getBytes();
+
+ p.writeByteArray(bytes);
+ }
+
+ // TODO: Should be mockable for testing?
+ // We cannot deserialize in the platform because we don't have a 'readFromProto'
+ // code.
+ public static @NonNull Intent create(Parcel p) {
+ // This will "read" the correct amount of data, but then we discard it.
+ byte[] data = p.createByteArray();
+
+ // Never called by real code in a platform, this binder API is implemented only in C++.
+ return new Intent("<cannot deserialize IntentProto>");
+ }
+ }
}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 7fcad360b8fe..9a30b35f02a2 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -24,12 +24,16 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Handler;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.wm.ActivityMetricsLaunchObserver;
@@ -43,10 +47,20 @@ import com.android.server.wm.ActivityTaskManagerInternal;
*/
public class IorapForwardingService extends SystemService {
- public static final boolean DEBUG = true; // TODO: read from a getprop?
public static final String TAG = "IorapForwardingService";
+ /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */
+ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /** $> adb shell 'setprop iorapd.enable true' */
+ private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true);
+ /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */
+ private static boolean WTF_CRASH = SystemProperties.getBoolean(
+ "iorapd.forwarding_service.wtf_crash", false);
private IIorap mIorapRemote;
+ private final Object mLock = new Object();
+ /** Handle onBinderDeath by periodically trying to reconnect. */
+ private final Handler mHandler =
+ new BinderConnectionHandler(IoThread.getHandler().getLooper());
/**
* Initializes the system service.
@@ -58,7 +72,7 @@ public class IorapForwardingService extends SystemService {
* @param context The system server context.
*/
public IorapForwardingService(Context context) {
- super(context);
+ super(context);
}
//<editor-fold desc="Providers">
@@ -78,12 +92,40 @@ public class IorapForwardingService extends SystemService {
@VisibleForTesting
protected IIorap provideIorapRemote() {
+ IIorap iorap;
try {
- return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
+ iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
} catch (ServiceManager.ServiceNotFoundException e) {
- // TODO: how do we handle service being missing?
- throw new AssertionError(e);
+ handleRemoteError(e);
+ return null;
}
+
+ try {
+ iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0);
+ } catch (RemoteException e) {
+ handleRemoteError(e);
+ return null;
+ }
+
+ return iorap;
+ }
+
+ @VisibleForTesting
+ protected DeathRecipient provideDeathRecipient() {
+ return new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.w(TAG, "iorapd has died");
+ retryConnectToRemoteAndConfigure(/*attempts*/0);
+ }
+ };
+ }
+
+ @VisibleForTesting
+ protected boolean isIorapEnabled() {
+ // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process
+ // never comes up, so all binder connections will fail indefinitely.
+ return IS_ENABLED;
}
//</editor-fold>
@@ -94,15 +136,128 @@ public class IorapForwardingService extends SystemService {
Log.v(TAG, "onStart");
}
+ retryConnectToRemoteAndConfigure(/*attempts*/0);
+ }
+
+ private class BinderConnectionHandler extends Handler {
+ public BinderConnectionHandler(android.os.Looper looper) {
+ super(looper);
+ }
+
+ public static final int MESSAGE_BINDER_CONNECT = 0;
+
+ private int mAttempts = 0;
+
+ @Override
+ public void handleMessage(android.os.Message message) {
+ switch (message.what) {
+ case MESSAGE_BINDER_CONNECT:
+ if (!retryConnectToRemoteAndConfigure(mAttempts)) {
+ mAttempts++;
+ } else {
+ mAttempts = 0;
+ }
+ break;
+ default:
+ throw new AssertionError("Unknown message: " + message.toString());
+ }
+ }
+ }
+
+ /**
+ * Handle iorapd shutdowns and crashes, by attempting to reconnect
+ * until the service is reached again.
+ *
+ * <p>The first connection attempt is synchronous,
+ * subsequent attempts are done by posting delayed tasks to the IoThread.</p>
+ *
+ * @return true if connection succeeded now, or false if it failed now [and needs to requeue].
+ */
+ private boolean retryConnectToRemoteAndConfigure(int attempts) {
+ final int sleepTime = 1000; // ms
+
+ if (DEBUG) {
+ Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts);
+ }
+
+ if (connectToRemoteAndConfigure()) {
+ return true;
+ }
+
+ // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually
+ // called 'adb shell stop iorapd' , which means this would loop until it comes back
+ // up.
+ //
+ // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid
+ // printing this warning.
+ Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime);
+
+ // Use a handler instead of Thread#sleep to avoid backing up the binder thread
+ // when this is called from the death recipient callback.
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT),
+ sleepTime);
+
+ return false;
+
+ // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts");
+ }
+
+ private boolean connectToRemoteAndConfigure() {
+ synchronized (mLock) {
+ // Synchronize against any concurrent calls to this via the DeathRecipient.
+ return connectToRemoteAndConfigureLocked();
+ }
+ }
+
+ private boolean connectToRemoteAndConfigureLocked() {
+ if (!isIorapEnabled()) {
+ if (DEBUG) {
+ Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work");
+ }
+ // When we see that iorapd is disabled (when system server comes up),
+ // it stays disabled permanently until the next system server reset.
+
+ // TODO: consider listening to property changes as a callback, then we can
+ // be more dynamic about handling enable/disable.
+ return true;
+ }
+
// Connect to the native binder service.
mIorapRemote = provideIorapRemote();
+ if (mIorapRemote == null) {
+ Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?");
+ return false;
+ }
invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+ registerInProcessListenersLocked();
+
+ return true;
+ }
+
+ private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
+ private boolean mRegisteredListeners = false;
+
+ private void registerInProcessListenersLocked() {
+ if (mRegisteredListeners) {
+ // Listeners are registered only once (idempotent operation).
+ //
+ // Today listeners are tolerant of the remote side going away
+ // by handling remote errors.
+ //
+ // We could try to 'unregister' the listener when we get a binder disconnect,
+ // but we'd still have to handle the case of encountering synchronous errors so
+ // it really wouldn't be a win (other than having less log spew).
+ return;
+ }
// Listen to App Launch Sequence events from ActivityTaskManager,
// and forward them to the native binder service.
ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
provideLaunchObserverRegistry();
- launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver());
+ launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
+
+ mRegisteredListeners = true;
}
private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
@@ -110,6 +265,8 @@ public class IorapForwardingService extends SystemService {
// launch sequences on the native side.
private @AppLaunchEvent.SequenceId long mSequenceId = -1;
+ // All callbacks occur on the same background thread. Don't synchronize explicitly.
+
@Override
public void onIntentStarted(@NonNull Intent intent) {
// #onIntentStarted [is the only transition that] initiates a new launch sequence.
@@ -174,7 +331,7 @@ public class IorapForwardingService extends SystemService {
invokeRemote(() ->
mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
- new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity))
+ new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
);
}
}
@@ -201,6 +358,7 @@ public class IorapForwardingService extends SystemService {
}
}
+ /** Allow passing lambdas to #invokeRemote */
private interface RemoteRunnable {
void run() throws RemoteException;
}
@@ -209,8 +367,26 @@ public class IorapForwardingService extends SystemService {
try {
r.run();
} catch (RemoteException e) {
- // TODO: what do we do with exceptions?
- throw new AssertionError("not implemented", e);
+ // This could be a logic error (remote side returning error), which we need to fix.
+ //
+ // This could also be a DeadObjectException in which case its probably just iorapd
+ // being manually restarted.
+ //
+ // Don't make any assumption, since DeadObjectException could also mean iorapd crashed
+ // unexpectedly.
+ //
+ // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath.
+ handleRemoteError(e);
}
}
+
+ private static void handleRemoteError(Throwable t) {
+ if (WTF_CRASH) {
+ // In development modes, we just want to crash.
+ throw new AssertionError("unexpected remote error", t);
+ } else {
+ // Log to wtf which gets sent to dropbox, and in system_server this does not crash.
+ Log.wtf(TAG, t);
+ }
+ }
}
diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml
index f83a16ec0916..919154d3e48a 100644
--- a/startop/iorap/tests/AndroidTest.xml
+++ b/startop/iorap/tests/AndroidTest.xml
@@ -33,6 +33,15 @@
<target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer">
</target_preparer>
+ <target_preparer
+ class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- Crash instead of using Log.wtf within the system_server iorap code. -->
+ <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" />
+ <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status -->
+ <option name="set-property" key="iorapd.binder.fake" value="true" />
+ <option name="restore-properties" value="true" />
+ </target_preparer>
+
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.google.android.startop.iorap.tests" />
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 2b99ce1d8252..2d29875aadb4 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -50,10 +50,10 @@ public class CallAttributes implements Parcelable {
}
private CallAttributes(Parcel in) {
- mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass()
- .getClassLoader());
+ mPreciseCallState = (PreciseCallState)
+ in.readValue(PreciseCallState.class.getClassLoader());
mNetworkType = in.readInt();
- mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader());
+ mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader());
}
// getters
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a33b44c454b4..349880d5ea3c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -608,11 +608,41 @@ public class CarrierConfigManager {
public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL =
"carrier_promote_wfc_on_call_fail_bool";
- /** Flag specifying whether provisioning is required for VOLTE. */
+ /**
+ * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
+ * Calling.
+ */
public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
= "carrier_volte_provisioning_required_bool";
/**
+ * Flag indicating whether or not the IMS MmTel UT capability requires carrier provisioning
+ * before it can be set as enabled.
+ *
+ * If true, the UT capability will be set to false for the newly loaded subscription
+ * and will require the carrier provisioning app to set the persistent provisioning result.
+ * If false, the platform will not wait for provisioning status updates for the UT capability
+ * and enable the UT over IMS capability for the subscription when the subscription is loaded.
+ *
+ * The default value for this key is {@code false}.
+ */
+ public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL =
+ "carrier_ut_provisioning_required_bool";
+
+ /**
+ * Flag indicating whether or not the carrier supports Supplementary Services over the UT
+ * interface for this subscription.
+ *
+ * If true, the device will use Supplementary Services over UT when provisioned (see
+ * {@link #KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL}). If false, this device will fallback to
+ * circuit switch for supplementary services and will disable this capability for IMS entirely.
+ *
+ * The default value for this key is {@code true}.
+ */
+ public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL =
+ "carrier_supports_ss_over_ut_bool";
+
+ /**
* Flag specifying if WFC provisioning depends on VoLTE provisioning.
*
* {@code false}: default value; honor actual WFC provisioning state.
@@ -2575,6 +2605,8 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d777bf123b67..aaca18a0d904 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,6 +16,8 @@
package android.telephony;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
@@ -2145,6 +2147,79 @@ public final class SmsManager {
}
/**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * The prefixes is a list of prefix {@code String} separated by this delimiter.
+ * @hide
+ */
+ public static final String REGEX_PREFIX_DELIMITER = ",";
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * The success status to be added into the intent to be sent to the calling package.
+ * @hide
+ */
+ public static final int RESULT_STATUS_SUCCESS = 0;
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * The timeout status to be added into the intent to be sent to the calling package.
+ * @hide
+ */
+ public static final int RESULT_STATUS_TIMEOUT = 1;
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * Intent extra key of the retrieved SMS message as a {@code String}.
+ * @hide
+ */
+ public static final String EXTRA_SMS_MESSAGE = "android.telephony.extra.SMS_MESSAGE";
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * Intent extra key of SMS retriever status, which indicates whether the request for the
+ * coming SMS message is SUCCESS or TIMEOUT
+ * @hide
+ */
+ public static final String EXTRA_STATUS = "android.telephony.extra.STATUS";
+ /**
+ * @see #createAppSpecificSmsTokenWithPackageInfo().
+ * [Optional] Intent extra key of the retrieved Sim card subscription Id if any. {@code int}
+ * @hide
+ */
+ public static final String EXTRA_SIM_SUBSCRIPTION_ID =
+ "android.telephony.extra.SIM_SUBSCRIPTION_ID";
+
+ /**
+ * Create a single use app specific incoming SMS request for the calling package.
+ *
+ * This method returns a token that if included in a subsequent incoming SMS message, and the
+ * SMS message has a prefix from the given prefixes list, the provided {@code intent} will be
+ * sent with the SMS data to the calling package.
+ *
+ * The token is only good for one use within a reasonable amount of time. After an SMS has been
+ * received containing the token all subsequent SMS messages with the token will be routed as
+ * normal.
+ *
+ * An app can only have one request at a time, if the app already has a request pending it will
+ * be replaced with a new request.
+ *
+ * @param prefixes this is a list of prefixes string separated by REGEX_PREFIX_DELIMITER. The
+ * matching SMS message should have at least one of the prefixes in the beginning of the
+ * message.
+ * @param intent this intent is sent when the matching SMS message is received.
+ * @return Token to include in an SMS message.
+ */
+ @Nullable
+ public String createAppSpecificSmsTokenWithPackageInfo(
+ @Nullable String prefixes, @NonNull PendingIntent intent) {
+ try {
+ ISms iccSms = getISmsServiceOrThrow();
+ return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
+ ActivityThread.currentPackageName(), prefixes, intent);
+
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
* Filters a bundle to only contain MMS config variables.
*
* This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e710e0e02c46..98fc7251e9a8 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6848,13 +6848,13 @@ public class TelephonyManager {
/**
* Values used to return status for hasCarrierPrivileges call.
*/
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1;
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0;
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1;
- /** @hide */ @SystemApi
+ /** @hide */ @SystemApi @TestApi
public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2;
/**
@@ -7056,6 +7056,7 @@ public class TelephonyManager {
/** @hide */
@SystemApi
+ @TestApi
@SuppressLint("Doclava125")
public int checkCarrierPrivilegesForPackage(String pkgName) {
try {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0fa1b41d4b16..bca088e618c0 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -91,20 +91,6 @@ public class EuiccManager {
"android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
/**
- * Intent action to select a profile to enable before download a new eSIM profile.
- *
- * May be called during device provisioning when there are multiple slots having profiles on
- * them. This Intent launches a screen for all the current existing profiles and let users to
- * choose which one they want to enable. In this case, the slot contains the profile will be
- * activated.
- *
- * @hide
- */
- @SystemApi
- public static final String ACTION_PROFILE_SELECTION =
- "android.telephony.euicc.action.PROFILE_SELECTION";
-
- /**
* Intent action to provision an embedded subscription.
*
* <p>May be called during device provisioning to launch a screen to perform embedded SIM
@@ -325,8 +311,8 @@ public class EuiccManager {
@IntDef(prefix = {"EUICC_ACTIVATION_"}, value = {
EUICC_ACTIVATION_TYPE_DEFAULT,
EUICC_ACTIVATION_TYPE_BACKUP,
- EUICC_ACTIVATION_TYPE_TRANSFER
-
+ EUICC_ACTIVATION_TYPE_TRANSFER,
+ EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED,
})
public @interface EuiccActivationType{}
@@ -360,6 +346,14 @@ public class EuiccManager {
@SystemApi
public static final int EUICC_ACTIVATION_TYPE_TRANSFER = 3;
+ /**
+ * The activation flow of eSIM requiring user account will be started. This can only be used
+ * when there is user account signed in. Otherwise, the flow will be failed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4;
/**
* Euicc OTA update status which can be got by {@link #getOtaStatus}
diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java
new file mode 100644
index 000000000000..ac4d17a0ce65
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsException.java
@@ -0,0 +1,113 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class defines an IMS-related exception that has been thrown while interacting with a
+ * device or carrier provided ImsService implementation.
+ * @hide
+ */
+@SystemApi
+public class ImsException extends Exception {
+
+ /**
+ * The operation has failed due to an unknown or unspecified error.
+ */
+ public static final int CODE_ERROR_UNSPECIFIED = 0;
+ /**
+ * The operation has failed because there is no {@link ImsService} available to service it. This
+ * may be due to an {@link ImsService} crash or other illegal state.
+ * <p>
+ * This is a temporary error and the operation may be retried until the connection to the
+ * {@link ImsService} is restored.
+ */
+ public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1;
+
+ /**
+ * This device or carrier configuration does not support IMS for this subscription.
+ * <p>
+ * This is a permanent configuration error and there should be no retry.
+ */
+ public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2;
+
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CODE_ERROR_", value = {
+ CODE_ERROR_UNSPECIFIED,
+ CODE_ERROR_SERVICE_UNAVAILABLE,
+ CODE_ERROR_UNSUPPORTED_OPERATION
+ })
+ public @interface ImsErrorCode {}
+
+ private int mCode = CODE_ERROR_UNSPECIFIED;
+
+ /**
+ * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code.
+ * @param message an optional message to detail the error condition more specifically.
+ */
+ public ImsException(@Nullable String message) {
+ super(getMessage(message, CODE_ERROR_UNSPECIFIED));
+ }
+
+ /**
+ * A new {@link ImsException} that includes an {@link ImsErrorCode} error code.
+ * @param message an optional message to detail the error condition more specifically.
+ */
+ public ImsException(@Nullable String message, @ImsErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ }
+
+ /**
+ * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a
+ * {@link Throwable} that contains the original error that was thrown to lead to this Exception.
+ * @param message an optional message to detail the error condition more specifically.
+ * @param cause the {@link Throwable} that caused this {@link ImsException} to be created.
+ */
+ public ImsException(@Nullable String message, @ImsErrorCode int code, Throwable cause) {
+ super(getMessage(message, code), cause);
+ mCode = code;
+ }
+
+ /**
+ * @return the IMS Error code that is associated with this {@link ImsException}.
+ */
+ public @ImsErrorCode int getCode() {
+ return mCode;
+ }
+
+ private static String getMessage(String message, int code) {
+ StringBuilder builder;
+ if (!TextUtils.isEmpty(message)) {
+ builder = new StringBuilder(message);
+ builder.append(" (code: ");
+ builder.append(code);
+ builder.append(")");
+ return builder.toString();
+ } else {
+ return "code: " + code;
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 9414abd98b1c..eb99d5dcaaeb 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -54,7 +54,7 @@ import java.util.concurrent.Executor;
* registration and MmTel capability status callbacks, as well as query/modify user settings for the
* associated subscription.
*
- * @see #createForSubscriptionId(Context, int)
+ * @see #createForSubscriptionId(int)
* @hide
*/
@SystemApi
@@ -86,9 +86,7 @@ public class ImsMmTelManager {
/**
* Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough.
- * @hide
*/
- @SystemApi
public static final int WIFI_MODE_WIFI_PREFERRED = 2;
/**
@@ -317,15 +315,12 @@ public class ImsMmTelManager {
/**
* Create an instance of ImsManager for the subscription id specified.
*
- * @param context The context to create this ImsMmTelManager instance within.
* @param subId The ID of the subscription that this ImsMmTelManager will use.
* @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
- * @throws IllegalArgumentException if the subscription is invalid or
- * the subscription ID is not an active subscription.
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
- public static ImsMmTelManager createForSubscriptionId(Context context, int subId) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)
- || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+ public static ImsMmTelManager createForSubscriptionId(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription ID");
}
@@ -333,7 +328,7 @@ public class ImsMmTelManager {
}
/**
- * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead.
+ * Only visible for testing, use {@link #createForSubscriptionId(int)} instead.
* @hide
*/
@VisibleForTesting
@@ -343,7 +338,7 @@ public class ImsMmTelManager {
/**
* Registers a {@link RegistrationCallback} with the system, which will provide registration
- * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use
+ * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use
* {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
* events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
*
@@ -356,13 +351,14 @@ public class ImsMmTelManager {
* @throws IllegalArgumentException if the subscription associated with this callback is not
* active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
* {@link CapabilityCallback} callback.
- * @throws IllegalStateException if the subscription associated with this callback is valid, but
+ * @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
- * the service crashed, for example.
+ * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+ * reason.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerImsRegistrationCallback(@CallbackExecutor Executor executor,
- @NonNull RegistrationCallback c) {
+ @NonNull RegistrationCallback c) throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
}
@@ -374,6 +370,8 @@ public class ImsMmTelManager {
getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
+ } catch (IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -405,7 +403,7 @@ public class ImsMmTelManager {
/**
* Registers a {@link CapabilityCallback} with the system, which will provide MmTel service
* availability updates for the subscription specified in
- * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)}
+ * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)}
* can also be used to query this information at any time.
*
* Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
@@ -421,13 +419,14 @@ public class ImsMmTelManager {
* @throws IllegalArgumentException if the subscription associated with this callback is not
* active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
* {@link CapabilityCallback} callback.
- * @throws IllegalStateException if the subscription associated with this callback is valid, but
+ * @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
- * the service crashed, for example.
+ * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+ * reason.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull CapabilityCallback c) {
+ @NonNull CapabilityCallback c) throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
}
@@ -439,6 +438,8 @@ public class ImsMmTelManager {
getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
+ } catch (IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -798,14 +799,6 @@ public class ImsMmTelManager {
}
}
- private static SubscriptionManager getSubscriptionManager(Context context) {
- SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
- if (manager == null) {
- throw new RuntimeException("Could not find SubscriptionManager.");
- }
- return manager;
- }
-
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
ServiceManager.getService(Context.TELEPHONY_SERVICE));
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index d37198a3e25d..b171f7940944 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -21,13 +21,17 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import com.android.internal.telephony.ITelephony;
@@ -38,13 +42,68 @@ import java.util.concurrent.Executor;
* to changes in these configurations.
*
* Note: IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
- * applications and may vary.
+ * applications and may vary. For compatibility purposes, the first 100 integer values used in
+ * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys
+ * previously defined in the Android framework. Some common constants have been defined in this
+ * class to make integrating with other system apps easier. USE WITH CARE!
+ *
+ * To avoid collisions, please use String based configurations when possible:
+ * {@link #setProvisioningStringValue(int, String)} and {@link #getProvisioningStringValue(int)}.
* @hide
*/
@SystemApi
public class ProvisioningManager {
/**
+ * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error.
+ */
+ public static final String STRING_QUERY_RESULT_ERROR_GENERIC =
+ "STRING_QUERY_RESULT_ERROR_GENERIC";
+
+ /**
+ * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the
+ * ImsService implementation was not ready for provisioning queries.
+ */
+ public static final String STRING_QUERY_RESULT_ERROR_NOT_READY =
+ "STRING_QUERY_RESULT_ERROR_NOT_READY";
+
+ /**
+ * The integer result of provisioning for the queried key is disabled.
+ */
+ public static final int PROVISIONING_VALUE_DISABLED = 0;
+
+ /**
+ * The integer result of provisioning for the queried key is enabled.
+ */
+ public static final int PROVISIONING_VALUE_ENABLED = 1;
+
+
+ /**
+ * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
+ * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
+ * the subscription for WiFi Calling.
+ *
+ * @see #getProvisioningIntValue(int)
+ * @see #setProvisioningIntValue(int, int)
+ */
+ public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26;
+
+ /**
+ * Override the user-defined WiFi mode for this subscription, defined in
+ * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning
+ * this subscription for WiFi Calling.
+ *
+ * Valid values for this key are:
+ * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY},
+ * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or
+ * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}.
+ *
+ * @see #getProvisioningIntValue(int)
+ * @see #setProvisioningIntValue(int, int)
+ */
+ public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27;
+
+ /**
* Callback for IMS provisioning changes.
*/
public static class Callback {
@@ -113,15 +172,13 @@ public class ProvisioningManager {
/**
* Create a new {@link ProvisioningManager} for the subscription specified.
- * @param context The context that this manager will use.
+ *
* @param subId The ID of the subscription that this ProvisioningManager will use.
* @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
- * @throws IllegalArgumentException if the subscription is invalid or
- * the subscription ID is not an active subscription.
+ * @throws IllegalArgumentException if the subscription is invalid.
*/
- public static ProvisioningManager createForSubscriptionId(Context context, int subId) {
- if (!SubscriptionManager.isValidSubscriptionId(subId)
- || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+ public static ProvisioningManager createForSubscriptionId(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid subscription ID");
}
@@ -143,18 +200,21 @@ public class ProvisioningManager {
* @see SubscriptionManager.OnSubscriptionsChangedListener
* @throws IllegalArgumentException if the subscription associated with this callback is not
* active (SIM is not inserted, ESIM inactive) or the subscription is invalid.
- * @throws IllegalStateException if the subscription associated with this callback is valid, but
+ * @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
- * the service crashed, for example.
+ * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+ * reason.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor,
- @NonNull Callback callback) {
+ @NonNull Callback callback) throws ImsException {
callback.setExecutor(executor);
try {
getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
+ } catch (IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -180,10 +240,15 @@ public class ProvisioningManager {
/**
* Query for the integer value associated with the provided key.
+ *
+ * This operation is blocking and should not be performed on the UI thread.
+ *
* @param key An integer that represents the provisioning key, which is defined by the OEM.
- * @return an integer value for the provided key.
+ * @return an integer value for the provided key, or
+ * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public int getProvisioningIntValue(int key) {
try {
@@ -195,10 +260,16 @@ public class ProvisioningManager {
/**
* Query for the String value associated with the provided key.
- * @param key An integer that represents the provisioning key, which is defined by the OEM.
- * @return a String value for the provided key, or {@code null} if the key doesn't exist.
+ *
+ * This operation is blocking and should not be performed on the UI thread.
+ *
+ * @param key A String that represents the provisioning key, which is defined by the OEM.
+ * @return a String value for the provided key, {@code null} if the key doesn't exist, or one
+ * of the following error codes: {@link #STRING_QUERY_RESULT_ERROR_GENERIC},
+ * {@link #STRING_QUERY_RESULT_ERROR_NOT_READY}.
* @throws IllegalArgumentException if the key provided was invalid.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getProvisioningStringValue(int key) {
try {
@@ -210,10 +281,16 @@ public class ProvisioningManager {
/**
* Set the integer value associated with the provided key.
+ *
+ * This operation is blocking and should not be performed on the UI thread.
+ *
+ * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined
+ * per OEM or carrier) when possible instead to avoid key collision if needed.
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @param value a integer value for the provided key.
* @return the result of setting the configuration value.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
try {
@@ -226,10 +303,14 @@ public class ProvisioningManager {
/**
* Set the String value associated with the provided key.
*
- * @param key An integer that represents the provisioning key, which is defined by the OEM.
+ * This operation is blocking and should not be performed on the UI thread.
+ *
+ * @param key A String that represents the provisioning key, which is defined by the OEM and
+ * should be appropriately namespaced to avoid collision.
* @param value a String value for the provided key.
* @return the result of setting the configuration value.
*/
+ @WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
String value) {
@@ -240,6 +321,57 @@ public class ProvisioningManager {
}
}
+ /**
+ * Set the provisioning status for the IMS MmTel capability using the specified subscription.
+ *
+ * Provisioning may or may not be required, depending on the carrier configuration. If
+ * provisioning is not required for the carrier associated with this subscription or the device
+ * does not support the capability/technology combination specified, this operation will be a
+ * no-op.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
+ * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+ * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
+ */
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setProvisioningStatusForCapability(
+ @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) {
+ try {
+ getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech,
+ isProvisioned);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Get the provisioning status for the IMS MmTel capability specified.
+ *
+ * If provisioning is not required for the queried
+ * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and
+ * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
+ * always return {@code true}.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
+ * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+ * @return true if the device is provisioned for the capability or does not require
+ * provisioning, false if the capability does require provisioning and has not been
+ * provisioned yet.
+ */
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean getProvisioningStatusForCapability(
+ @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+ try {
+ return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
private static SubscriptionManager getSubscriptionManager(Context context) {
SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
if (manager == null) {
diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 7c793a5c18ac..1ee85633c6dc 100644
--- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
@@ -97,6 +97,13 @@ public final class CapabilityChangeRequest implements Parcelable {
public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() {
return radioTech;
}
+
+ @Override
+ public String toString() {
+ return "CapabilityPair{"
+ + "mCapability=" + mCapability
+ + ", radioTech=" + radioTech + '}';
+ }
}
// Pair contains <radio tech, mCapability>
@@ -212,6 +219,13 @@ public final class CapabilityChangeRequest implements Parcelable {
}
}
+ @Override
+ public String toString() {
+ return "CapabilityChangeRequest{"
+ + "mCapabilitiesToEnable=" + mCapabilitiesToEnable
+ + ", mCapabilitiesToDisable=" + mCapabilitiesToDisable + '}';
+ }
+
/**
* @hide
*/
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 71a21743a449..4fc6a19d1f38 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -277,12 +277,14 @@ public class ImsConfig {
* Wi-Fi calling roaming status.
* Value is in Integer format. ON (1), OFF(0).
*/
- public static final int VOICE_OVER_WIFI_ROAMING = 26;
+ public static final int VOICE_OVER_WIFI_ROAMING =
+ ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE;
/**
* Wi-Fi calling modem - WfcModeFeatureValueConstants.
* Value is in Integer format.
*/
- public static final int VOICE_OVER_WIFI_MODE = 27;
+ public static final int VOICE_OVER_WIFI_MODE =
+ ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE;
/**
* VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0).
* Value is in Integer format.
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index f35e88672a23..fea763ed5785 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -21,8 +21,10 @@ import android.telephony.ims.ImsReasonInfo;
/**
* This class defines a general IMS-related exception.
*
+ * @deprecated Use {@link android.telephony.ims.ImsException} instead.
* @hide
*/
+@Deprecated
public class ImsException extends Exception {
/**
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index a4eb424ab66e..58ebb159ea9f 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -560,4 +560,19 @@ interface ISms {
* @param intent PendingIntent to be sent when an SMS is received containing the token.
*/
String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent);
+
+ /**
+ * Create an app-only incoming SMS request for the calling package.
+ *
+ * If an incoming text contains the token returned by this method the provided
+ * <code>PendingIntent</code> will be sent containing the SMS data.
+ *
+ * @param subId the SIM id.
+ * @param callingPkg the package name of the calling app.
+ * @param prefixes the caller provided prefixes
+ * @param intent PendingIntent to be sent when a SMS is received containing the token and one
+ * of the prefixes
+ */
+ String createAppSpecificSmsTokenWithPackageInfo(
+ int subId, String callingPkg, String prefixes, in PendingIntent intent);
}
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 1cdf44d897b2..f2f2960e92a4 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -188,4 +188,10 @@ public class ISmsImplBase extends ISms.Stub {
public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public String createAppSpecificSmsTokenWithPackageInfo(
+ int subId, String callingPkg, String prefixes, PendingIntent intent) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9cc173cfcdd6..8237d39c4c3d 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1765,6 +1765,24 @@ interface ITelephony {
void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
/**
+ * Set the provisioning status for the IMS MmTel capability using the specified subscription.
+ */
+ void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
+ boolean isProvisioned);
+
+ /**
+ * Get the provisioning status for the IMS MmTel capability specified.
+ */
+ boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech);
+
+ /** Is the capability and tech flagged as provisioned in the cache */
+ boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
+
+ /** Set the provisioning for the capability and tech in the cache */
+ void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech,
+ boolean isProvisioned);
+
+ /**
* Return an integer containing the provisioning value for the specified provisioning key.
*/
int getImsProvisioningInt(int subId, int key);
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 0edc0026722b..c76d153c6112 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -284,10 +284,6 @@ public final class TelephonyPermissions {
*/
private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
int uid, String callingPackage, String message) {
- // If the device identifier check is enabled then enforce the new access requirements for
- // both 1P and 3P apps.
- boolean enableDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED, 0) == 1;
// Check if the application is a 3P app; if so then a separate setting is required to relax
// the check to begin flagging problems with 3P apps early.
boolean relax3PDeviceIdentifierCheck = Settings.Global.getInt(context.getContentResolver(),
@@ -300,6 +296,11 @@ public final class TelephonyPermissions {
context.getContentResolver(),
Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED, 0) == 1;
boolean isNonPrivApp = false;
+ // Similar to above support relaxing the check for privileged apps while still enforcing it
+ // for non-privileged and 3P apps.
+ boolean relaxPrivDeviceIdentifierCheck = Settings.Global.getInt(
+ context.getContentResolver(),
+ Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_PRIV_CHECK_RELAXED, 0) == 1;
ApplicationInfo callingPackageInfo = null;
try {
callingPackageInfo = context.getPackageManager().getApplicationInfo(callingPackage, 0);
@@ -315,37 +316,28 @@ public final class TelephonyPermissions {
Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
e);
}
- Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
- + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp);
- // The new Q restrictions for device identifier access will be enforced if any of the
- // following are true:
- // - The PRIVILEGED_DEVICE_IDENTIFIER_CHECK_ENABLED setting has been set.
- // - The app requesting a device identifier is not a preloaded app (3P), and the
- // PRIVILEGED_DEVICE_IDENTIFIER_3P_CHECK_RELAXED setting has not been set.
- // - The app requesting a device identifier is a preloaded app but is not a privileged app,
- // and the PRIVILEGED_DEVICE_IDENTIFIER_NON_PRIV_CHECK_RELAXED setting has not been set.
- if (enableDeviceIdentifierCheck
+ // The new Q restrictions for device identifier access will be enforced for all apps with
+ // settings to individually disable the new restrictions for privileged, preloaded
+ // non-privileged, and 3P apps.
+ if ((!is3PApp && !isNonPrivApp && !relaxPrivDeviceIdentifierCheck)
|| (is3PApp && !relax3PDeviceIdentifierCheck)
|| (isNonPrivApp && !relaxNonPrivDeviceIdentifierCheck)) {
- boolean targetQBehaviorDisabled = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.PRIVILEGED_DEVICE_IDENTIFIER_TARGET_Q_BEHAVIOR_ENABLED, 0) == 0;
- if (callingPackage != null) {
- // if the target SDK is pre-Q or the target Q behavior is disabled then check if
- // the calling package would have previously had access to device identifiers.
- if (callingPackageInfo != null && (
- callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q
- || targetQBehaviorDisabled)) {
- if (context.checkPermission(
- android.Manifest.permission.READ_PHONE_STATE,
- pid,
- uid) == PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- if (SubscriptionManager.isValidSubscriptionId(subId)
- && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
- == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- return false;
- }
+ Log.wtf(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message
+ + ":is3PApp=" + is3PApp + ":isNonPrivApp=" + isNonPrivApp);
+ // if the target SDK is pre-Q then check if the calling package would have previously
+ // had access to device identifiers.
+ if (callingPackageInfo != null && (
+ callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
+ if (context.checkPermission(
+ android.Manifest.permission.READ_PHONE_STATE,
+ pid,
+ uid) == PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return false;
}
}
throw new SecurityException(message + ": The user " + uid
diff --git a/test-mock/src/android/test/mock/MockCursor.java b/test-mock/src/android/test/mock/MockCursor.java
index 576f24ad6384..f69db2c84111 100644
--- a/test-mock/src/android/test/mock/MockCursor.java
+++ b/test-mock/src/android/test/mock/MockCursor.java
@@ -24,6 +24,8 @@ import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;
+import java.util.List;
+
/**
* A mock {@link android.database.Cursor} class that isolates the test code from real
* Cursor implementation.
@@ -226,11 +228,21 @@ public class MockCursor implements Cursor {
}
@Override
+ public void setNotificationUris(ContentResolver cr, List<Uri> uris) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
public Uri getNotificationUri() {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
+ public List<Uri> getNotificationUris() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index 979d13ac9405..ee02a72b6819 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -35,7 +35,6 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE := DexLoggerNativeTestLibrary
-LOCAL_MULTILIB := first
LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE)
@@ -44,8 +43,6 @@ LOCAL_NDK_STL_VARIANT := c++_static
include $(BUILD_SHARED_LIBRARY)
-dexloggertest_so := $(LOCAL_BUILT_MODULE)
-
# And a standalone native executable that we can exec.
include $(CLEAR_VARS)
@@ -73,11 +70,15 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
truth-prebuilt \
+# Include both versions of the .so if we have 2 arch
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := \
+ DexLoggerNativeTestLibrary \
+
# This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
# native binaries.
LOCAL_JAVA_RESOURCE_FILES := \
$(dexloggertest_jar) \
- $(dexloggertest_so) \
- $(dexloggertest_executable)
+ $(dexloggertest_executable) \
include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d68769b378b9..e92cc56322eb 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import android.app.UiAutomation;
import android.content.Context;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
@@ -147,7 +148,7 @@ public final class DexLoggerIntegrationTests {
String expectedNameHash =
"996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
String expectedContentHash =
- copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+ copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
System.load(privateCopyFile.toString());
@@ -170,7 +171,7 @@ public final class DexLoggerIntegrationTests {
String expectedNameHash =
"8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
String expectedContentHash =
- copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+ copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
System.load(privateCopyFile.toString());
@@ -307,6 +308,12 @@ public final class DexLoggerIntegrationTests {
return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
}
+ private String libraryPath(final String libraryName) {
+ // This may be deprecated. but it tells us the ABI of this process which is exactly what we
+ // want.
+ return "/lib/" + Build.CPU_ABI + "/" + libraryName;
+ }
+
private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
MessageDigest hasher = MessageDigest.getInstance("SHA-256");
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index 07c4a938cf9f..c16efbda1830 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -70,6 +70,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity {
R.id.benchmark_text_low_hitrate,
R.id.benchmark_edit_text_input,
R.id.benchmark_overdraw,
+ R.id.benchmark_bitmap_upload,
};
public static class LocalBenchmarksList extends ListFragment {
@@ -204,6 +205,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity {
case R.id.benchmark_text_low_hitrate:
case R.id.benchmark_edit_text_input:
case R.id.benchmark_overdraw:
+ case R.id.benchmark_bitmap_upload:
case R.id.benchmark_memory_bandwidth:
case R.id.benchmark_memory_latency:
case R.id.benchmark_power_management:
@@ -323,6 +325,9 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity {
intent = new Intent(getApplicationContext(), EditTextInputActivity.class);
break;
case R.id.benchmark_overdraw:
+ intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class);
+ break;
+ case R.id.benchmark_bitmap_upload:
intent = new Intent(getApplicationContext(), BitmapUploadActivity.class);
break;
case R.id.benchmark_memory_bandwidth:
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
index 89c6aedd8b5c..5723c599d91a 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
@@ -229,6 +229,8 @@ public class BenchmarkRegistry {
return context.getString(R.string.cpu_gflops_name);
case R.id.benchmark_overdraw:
return context.getString(R.string.overdraw_name);
+ case R.id.benchmark_bitmap_upload:
+ return context.getString(R.string.bitmap_upload_name);
default:
return "Some Benchmark";
}
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
index 787090208d7e..7692836cfacc 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
@@ -32,6 +32,7 @@ import android.view.MotionEvent;
import android.view.View;
import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkRegistry;
import com.android.benchmark.ui.automation.Automator;
import com.android.benchmark.ui.automation.Interaction;
@@ -124,7 +125,9 @@ public class BitmapUploadActivity extends AppCompatActivity {
final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
- mAutomator = new Automator("BMUpload", runId, iteration, getWindow(),
+ String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload);
+
+ mAutomator = new Automator(name, runId, iteration, getWindow(),
new Automator.AutomateCallback() {
@Override
public void onPostAutomate() {
diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml
index 6801fd9f61ec..694e0d9a6917 100644
--- a/tests/JankBench/app/src/main/res/values/ids.xml
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -23,6 +23,7 @@
<item name="benchmark_text_low_hitrate" type="id" />
<item name="benchmark_edit_text_input" type="id" />
<item name="benchmark_overdraw" type="id" />
+ <item name="benchmark_bitmap_upload" type="id" />
<item name="benchmark_memory_bandwidth" type="id" />
<item name="benchmark_memory_latency" type="id" />
<item name="benchmark_power_management" type="id" />
diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml
index 270adf89e4ed..5c2405899db9 100644
--- a/tests/JankBench/app/src/main/res/values/strings.xml
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -33,6 +33,8 @@
<string name="edit_text_input_description">Tests edit text input</string>
<string name="overdraw_name">Overdraw Test</string>
<string name="overdraw_description">Tests how the device handles overdraw</string>
+ <string name="bitmap_upload_name">Bitmap Upload Test</string>
+ <string name="bitmap_upload_description">Tests bitmap upload</string>
<string name="memory_bandwidth_name">Memory Bandwidth</string>
<string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
<string name="memory_latency_name">Memory Latency</string>
diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml
index 07c453c25359..fccc7b9d3776 100644
--- a/tests/JankBench/app/src/main/res/xml/benchmark.xml
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -62,6 +62,12 @@
benchmark:category="ui"
benchmark:description="@string/overdraw_description" />
+ <com.android.benchmark.Benchmark
+ benchmark:name="@string/bitmap_upload_name"
+ benchmark:id="@id/benchmark_bitmap_upload"
+ benchmark:category="ui"
+ benchmark:description="@string/bitmap_upload_description" />
+
<!--
<com.android.benchmark.Benchmark
benchmark:name="@string/memory_bandwidth_name"
diff --git a/tests/RollbackTest/Android.mk b/tests/RollbackTest/Android.mk
index 34aa258bf465..780bb24e437b 100644
--- a/tests/RollbackTest/Android.mk
+++ b/tests/RollbackTest/Android.mk
@@ -36,6 +36,17 @@ LOCAL_PACKAGE_NAME := RollbackTestAppAv2
include $(BUILD_PACKAGE)
ROLLBACK_TEST_APP_AV2 := $(LOCAL_INSTALLED_MODULE)
+# RollbackTestAppACrashingV2.apk
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, TestApp/src)
+LOCAL_MANIFEST_FILE := TestApp/ACrashingV2.xml
+LOCAL_PACKAGE_NAME := RollbackTestAppACrashingV2
+include $(BUILD_PACKAGE)
+ROLLBACK_TEST_APP_A_CRASHING_V2 := $(LOCAL_INSTALLED_MODULE)
+
# RollbackTestAppBv1.apk
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
@@ -68,6 +79,7 @@ LOCAL_COMPATIBILITY_SUITE := general-tests
LOCAL_JAVA_RESOURCE_FILES := \
$(ROLLBACK_TEST_APP_AV1) \
$(ROLLBACK_TEST_APP_AV2) \
+ $(ROLLBACK_TEST_APP_A_CRASHING_V2) \
$(ROLLBACK_TEST_APP_BV1) \
$(ROLLBACK_TEST_APP_BV2)
LOCAL_SDK_VERSION := system_current
@@ -77,5 +89,6 @@ include $(BUILD_PACKAGE)
# Clean up local variables
ROLLBACK_TEST_APP_AV1 :=
ROLLBACK_TEST_APP_AV2 :=
+ROLLBACK_TEST_APP_A_CRASHING_V2 :=
ROLLBACK_TEST_APP_BV1 :=
ROLLBACK_TEST_APP_BV2 :=
diff --git a/tests/RollbackTest/TestApp/ACrashingV2.xml b/tests/RollbackTest/TestApp/ACrashingV2.xml
new file mode 100644
index 000000000000..5708d2385f01
--- /dev/null
+++ b/tests/RollbackTest/TestApp/ACrashingV2.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.rollback.testapp.A"
+ android:versionCode="2"
+ android:versionName="2.0" >
+
+
+ <uses-sdk android:minSdkVersion="19" />
+
+ <application android:label="Rollback Test App A v2">
+ <meta-data android:name="version" android:value="2" />
+ <receiver android:name="com.android.tests.rollback.testapp.ProcessUserData"
+ android:exported="true" />
+ <activity android:name="com.android.tests.rollback.testapp.CrashingMainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
index cddb91f65d0f..02a439b5dd69 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/tests/RollbackTest/TestApp/src/com/android/tests/rollback/testapp/CrashingMainActivity.java
@@ -14,16 +14,20 @@
* limitations under the License.
*/
-package android.net.dhcp;
+package com.android.tests.rollback.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
/**
- * TODO: remove this class after migrating clients.
+ * A crashing test app for testing apk rollback support.
*/
-public class DhcpClient {
- public static final int CMD_PRE_DHCP_ACTION = 1003;
- public static final int CMD_POST_DHCP_ACTION = 1004;
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
+public class CrashingMainActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- public static final int DHCP_SUCCESS = 1;
- public static final int DHCP_FAILURE = 2;
+ throw new RuntimeException("Intended force crash");
+ }
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
index 030641bf0895..e10f866c899f 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
/**
* A broadcast receiver that can be used to get
- * ACTION_PACKAGE_ROLLBACK_EXECUTED broadcasts.
+ * ACTION_ROLLBACK_COMMITTED broadcasts.
*/
class RollbackBroadcastReceiver extends BroadcastReceiver {
@@ -43,7 +43,7 @@ class RollbackBroadcastReceiver extends BroadcastReceiver {
*/
RollbackBroadcastReceiver() {
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+ filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
InstrumentationRegistry.getContext().registerReceiver(this, filter);
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 9d67cea05fc8..7ab716fac6df 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -31,17 +31,16 @@ import android.support.test.InstrumentationRegistry;
import android.util.Log;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -57,6 +56,7 @@ public class RollbackTest {
private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A";
private static final String TEST_APP_B = "com.android.tests.rollback.testapp.B";
+ private static final String INSTRUMENTED_APP = "com.android.tests.rollback";
/**
* Test basic rollbacks.
@@ -84,8 +84,8 @@ public class RollbackTest {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.MANAGE_ROLLBACKS);
- // Register a broadcast receiver for notification when the rollback is
- // done executing.
+ // Register a broadcast receiver for notification when the
+ // rollback has been committed.
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
RollbackManager rm = RollbackTestUtils.getRollbackManager();
@@ -97,12 +97,11 @@ public class RollbackTest {
// uninstalled and when rollback manager deletes the rollback. Fix it
// so that's not the case!
for (int i = 0; i < 5; ++i) {
- for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
- Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
- Thread.sleep(1000);
- break;
- }
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+ if (rollback != null) {
+ Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
+ Thread.sleep(1000);
}
}
@@ -111,13 +110,11 @@ public class RollbackTest {
// between when the app is uninstalled and when the previously
// available rollback, if any, is removed.
Thread.sleep(1000);
- assertNull(rm.getAvailableRollback(TEST_APP_A));
- assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+ assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
- // There should be no recently executed rollbacks for this package.
- for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
- }
+ // There should be no recently committed rollbacks for this package.
+ assertNull(getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A));
// Install v1 of the app (without rollbacks enabled).
RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -132,10 +129,9 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -154,15 +150,9 @@ public class RollbackTest {
assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
// Verify the recent rollback has been recorded.
- rollback = null;
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
- assertNull(rollback);
- rollback = r;
- }
- }
- assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
broadcastReceiver.unregister();
context.unregisterReceiver(enableRollbackReceiver);
@@ -200,28 +190,25 @@ public class RollbackTest {
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
- rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Rollback of B should not rollback A
RollbackTestUtils.rollback(rollbackB);
@@ -262,28 +249,23 @@ public class RollbackTest {
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoForAandB(rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoForAandB(rollbackB);
// Reload the persisted data.
rm.reloadPersistedData();
// The apps should still be available for rollback.
- rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+ rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoForAandB(rollbackA);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
- rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+ rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoForAandB(rollbackB);
// Rollback of B should rollback A as well
RollbackTestUtils.rollback(rollbackB);
@@ -295,10 +277,10 @@ public class RollbackTest {
}
/**
- * Test that recently executed rollback data is properly persisted.
+ * Test that recently committed rollback data is properly persisted.
*/
@Test
- public void testRecentlyExecutedRollbackPersistence() throws Exception {
+ public void testRecentlyCommittedRollbackPersistence() throws Exception {
try {
RollbackTestUtils.adoptShellPermissionIdentity(
Manifest.permission.INSTALL_PACKAGES,
@@ -317,37 +299,27 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
// Roll back the app.
RollbackTestUtils.rollback(rollback);
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
// Verify the recent rollback has been recorded.
- rollback = null;
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
- assertNull(rollback);
- rollback = r;
- }
- }
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// Reload the persisted data.
rm.reloadPersistedData();
// Verify the recent rollback is still recorded.
- rollback = null;
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
- assertNull(rollback);
- rollback = r;
- }
- }
+ rollback = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -376,17 +348,16 @@ public class RollbackTest {
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollback);
- assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
// Expire the rollback.
rm.expireRollbackForPackage(TEST_APP_A);
// The rollback should no longer be available.
- assertNull(rm.getAvailableRollback(TEST_APP_A));
- assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+ assertNull(getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A));
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -457,7 +428,8 @@ public class RollbackTest {
processUserData(TEST_APP_A);
RollbackManager rm = RollbackTestUtils.getRollbackManager();
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
RollbackTestUtils.rollback(rollback);
processUserData(TEST_APP_A);
} finally {
@@ -467,12 +439,12 @@ public class RollbackTest {
/**
* Test restrictions on rollback broadcast sender.
- * A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast.
+ * A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
*/
@Test
public void testRollbackBroadcastRestrictions() throws Exception {
RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
- Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+ Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
try {
InstrumentationRegistry.getContext().sendBroadcast(broadcast);
fail("Succeeded in sending restricted broadcast from app context.");
@@ -514,18 +486,18 @@ public class RollbackTest {
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// Both test apps should now be available for rollback, and the
- // targetPackage returned for rollback should be correct.
+ // RollbackInfo returned for the rollbacks should be correct.
// TODO: See if there is a way to remove this race condition
// between when the app is installed and when the rollback
// is made available.
Thread.sleep(1000);
- RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollbackA);
- assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
- RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
- assertNotNull(rollbackB);
- assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
// Executing rollback should roll back the correct package.
RollbackTestUtils.rollback(rollbackA);
@@ -556,21 +528,14 @@ public class RollbackTest {
RollbackManager rm = RollbackTestUtils.getRollbackManager();
try {
- rm.getAvailableRollback(TEST_APP_A);
- fail("expected SecurityException");
- } catch (SecurityException e) {
- // Expected.
- }
-
- try {
- rm.getPackagesWithAvailableRollbacks();
+ rm.getAvailableRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
}
try {
- rm.getRecentlyExecutedRollbacks();
+ rm.getRecentlyCommittedRollbacks();
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -579,7 +544,7 @@ public class RollbackTest {
try {
// TODO: What if the implementation checks arguments for non-null
// first? Then this test isn't valid.
- rm.executeRollback(null, null);
+ rm.commitRollback(null, null);
fail("expected SecurityException");
} catch (SecurityException e) {
// Expected.
@@ -627,12 +592,9 @@ public class RollbackTest {
assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
// TEST_APP_A should now be available for rollback.
- assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
- RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
- assertNotNull(rollback);
-
- // TODO: Test the dependent apps for rollback are correct once we
- // support that in the RollbackInfo API.
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoForAandB(rollback);
// Rollback the app. It should cause both test apps to be rolled
// back.
@@ -640,14 +602,16 @@ public class RollbackTest {
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
- // We should not see a recent rollback listed for TEST_APP_B
- for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
- assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
- }
+ // We should see recent rollbacks listed for both A and B.
+ Thread.sleep(1000);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getRecentlyCommittedRollbacks(), TEST_APP_B);
+ assertRollbackInfoForAandB(rollbackB);
- // TODO: Test the listed dependent apps for the recently executed
- // rollback are correct once we support that in the RollbackInfo
- // API.
+ assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId());
} finally {
RollbackTestUtils.dropShellPermissionIdentity();
}
@@ -663,4 +627,106 @@ public class RollbackTest {
assertEquals(packageName, info.getVersionRolledBackTo().getPackageName());
assertEquals(versionRolledBackTo, info.getVersionRolledBackTo().getLongVersionCode());
}
+
+ // TODO(zezeozue): Stop ignoring after fixing race between rolling back and testing version
+ /**
+ * Test bad update automatic rollback.
+ */
+ @Ignore("Flaky")
+ @Test
+ public void testBadUpdateRollback() throws Exception {
+ try {
+ RollbackTestUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+ RollbackManager rm = RollbackTestUtils.getRollbackManager();
+
+ // Prep installation of the test apps.
+ RollbackTestUtils.uninstall(TEST_APP_A);
+ RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppACrashingV2.apk", true);
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+
+ RollbackTestUtils.uninstall(TEST_APP_B);
+ RollbackTestUtils.install("RollbackTestAppBv1.apk", false);
+ RollbackTestUtils.install("RollbackTestAppBv2.apk", true);
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+
+ // Both test apps should now be available for rollback, and the
+ // targetPackage returned for rollback should be correct.
+ // TODO: See if there is a way to remove this race condition
+ // between when the app is installed and when the rollback
+ // is made available.
+ Thread.sleep(1000);
+ RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_A);
+ assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
+
+ RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TEST_APP_B);
+ assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
+
+ // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes
+ for (int i = 0; i < 5; i++) {
+ RollbackTestUtils.launchPackage(TEST_APP_A);
+ Thread.sleep(1000);
+ }
+ Thread.sleep(1000);
+
+ // TEST_APP_A is automatically rolled back by the RollbackPackageHealthObserver
+ assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
+ // Instrumented app is still the package installer
+ Context context = InstrumentationRegistry.getContext();
+ String installer = context.getPackageManager().getInstallerPackageName(TEST_APP_A);
+ assertEquals(INSTRUMENTED_APP, installer);
+ // TEST_APP_B is untouched
+ assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
+ } finally {
+ RollbackTestUtils.dropShellPermissionIdentity();
+ }
+ }
+
+ // Helper function to test the value of a RollbackInfo with single package
+ private void assertRollbackInfoEquals(String packageName,
+ long versionRolledBackFrom, long versionRolledBackTo,
+ RollbackInfo info) {
+ assertNotNull(info);
+ assertEquals(1, info.getPackages().size());
+ assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
+ info.getPackages().get(0));
+ }
+
+ // Helper function to test that the given rollback info is a rollback for
+ // the atomic set {A2, B2} -> {A1, B1}.
+ private void assertRollbackInfoForAandB(RollbackInfo rollback) {
+ assertNotNull(rollback);
+ assertEquals(2, rollback.getPackages().size());
+ if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) {
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0));
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1));
+ } else {
+ assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0));
+ assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
+ }
+ }
+
+ // Helper function to return the RollbackInfo with a given package in the
+ // list of rollbacks. Throws an assertion failure if there is more than
+ // one such rollback info. Returns null if there are no such rollback
+ // infos.
+ private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
+ String packageName) {
+ RollbackInfo found = null;
+ for (RollbackInfo rollback : rollbacks) {
+ for (PackageRollbackInfo info : rollback.getPackages()) {
+ if (packageName.equals(info.getPackageName())) {
+ assertNull(found);
+ found = rollback;
+ break;
+ }
+ }
+ }
+ return found;
+ }
}
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index fbc3d8f1cd34..f481897c060c 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -90,12 +90,12 @@ class RollbackTestUtils {
}
/**
- * Execute the given rollback.
+ * Commit the given rollback.
* @throws AssertionError if the rollback fails.
*/
static void rollback(RollbackInfo rollback) throws InterruptedException {
RollbackManager rm = getRollbackManager();
- rm.executeRollback(rollback, LocalIntentSender.getIntentSender());
+ rm.commitRollback(rollback, LocalIntentSender.getIntentSender());
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
}
@@ -135,6 +135,17 @@ class RollbackTestUtils {
assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
}
+ /** Launches {@code packageName} with {@link Intent#ACTION_MAIN}. */
+ static void launchPackage(String packageName)
+ throws InterruptedException, IOException {
+ Context context = InstrumentationRegistry.getContext();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setPackage(packageName);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ context.startActivity(intent);
+ }
+
/**
* Installs the apks with the given resource names as an atomic set.
*
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index 4b1c1bd69920..fefd99394a87 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
+ <uses-permission android:name="android.permission.SUSPEND_APPS" />
<application android:label="Usage Access Test">
<activity android:name=".UsageStatsActivity"
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
index 612267c85b1b..272e0f4e1f54 100644
--- a/tests/UsageStatsTest/res/menu/main.xml
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -6,4 +6,6 @@
android:title="Call isAppInactive()"/>
<item android:id="@+id/set_app_limit"
android:title="Set App Limit" />
+ <item android:id="@+id/set_app_usage_limit"
+ android:title="Set App Usage Limit" />
</menu>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 3c628f6e0013..0105893adf9e 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -21,6 +21,8 @@ import android.app.ListActivity;
import android.app.PendingIntent;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -49,6 +51,8 @@ public class UsageStatsActivity extends ListActivity {
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT";
private UsageStatsManager mUsageStatsManager;
+ private ClipboardManager mClipboard;
+ private ClipData mClip;
private Adapter mAdapter;
private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() {
@Override
@@ -61,6 +65,7 @@ public class UsageStatsActivity extends ListActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+ mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
mAdapter = new Adapter();
setListAdapter(mAdapter);
Bundle extras = getIntent().getExtras();
@@ -98,6 +103,8 @@ public class UsageStatsActivity extends ListActivity {
case R.id.set_app_limit:
callSetAppLimit();
return true;
+ case R.id.set_app_usage_limit:
+ callSetAppUsageLimit();
default:
return super.onOptionsItemSelected(item);
}
@@ -170,6 +177,40 @@ public class UsageStatsActivity extends ListActivity {
builder.show();
}
+ private void callSetAppUsageLimit() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Enter package name");
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setHint("com.android.tests.usagestats");
+ builder.setView(input);
+
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String packageName = input.getText().toString().trim();
+ if (!TextUtils.isEmpty(packageName)) {
+ String[] packages = packageName.split(",");
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class);
+ intent.setPackage(getPackageName());
+ intent.putExtra(EXTRA_KEY_TIMEOUT, true);
+ mUsageStatsManager.registerAppUsageLimitObserver(1, packages,
+ 60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
+ 1, intent, 0));
+ }
+ }
+ });
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
private void showInactive(String packageName) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(
@@ -232,6 +273,21 @@ public class UsageStatsActivity extends ListActivity {
holder.packageName.setText(mStats.get(position).getPackageName());
holder.usageTime.setText(DateUtils.formatDuration(
mStats.get(position).getTotalTimeInForeground()));
+
+ //copy package name to the clipboard for convenience
+ holder.packageName.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ String text = holder.packageName.getText().toString();
+ mClip = ClipData.newPlainText("package_name", text);
+ mClipboard.setPrimaryClip(mClip);
+
+ Toast.makeText(getApplicationContext(), "package name copied to clipboard",
+ Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ });
+
return convertView;
}
}
diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/java/android/net/StaticIpConfigurationTest.java
index 5bb573455358..2b5ad378e0ae 100644
--- a/tests/net/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/java/android/net/StaticIpConfigurationTest.java
@@ -26,13 +26,13 @@ import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Objects;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class StaticIpConfigurationTest {
@@ -203,7 +203,7 @@ public class StaticIpConfigurationTest {
try {
s.writeToParcel(p, 0);
p.setDataPosition(0);
- s2 = StaticIpConfiguration.CREATOR.createFromParcel(p);
+ s2 = StaticIpConfiguration.readFromParcel(p);
} finally {
p.recycle();
}
diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
index 14df392cbe07..fb4d43c367db 100644
--- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -62,7 +62,7 @@ public class IpConfigurationParcelableUtilTest {
mDhcpResults.leaseDuration = 3600;
mDhcpResults.mtu = 1450;
// Any added DhcpResults field must be included in equals() to be tested properly
- assertFieldCountEquals(4, DhcpResults.class);
+ assertFieldCountEquals(8, DhcpResults.class);
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b5d5f61b527e..923c7dd5fb94 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -123,6 +123,7 @@ import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkUtils;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.SocketKeepalive;
import android.net.UidRange;
@@ -161,6 +162,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
@@ -1007,6 +1009,11 @@ public class ConnectivityServiceTest {
}
@Override
+ protected ProxyTracker makeProxyTracker() {
+ return mock(ProxyTracker.class);
+ }
+
+ @Override
protected int reserveNetId() {
while (true) {
final int netId = super.reserveNetId();
@@ -1028,6 +1035,11 @@ public class ConnectivityServiceTest {
}
}
+ @Override
+ protected boolean queryUserAccess(int uid, int netId) {
+ return true;
+ }
+
public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
@@ -5132,4 +5144,84 @@ public class ConnectivityServiceTest {
mCellNetworkAgent.sendLinkProperties(lp);
verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES);
}
+
+ @Test
+ public void testGetGlobalProxyForNetwork() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+ when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo);
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
+ }
+
+ @Test
+ public void testGetProxyForActiveNetwork() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ final LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
+
+ @Test
+ public void testGetProxyForVPN() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+
+ // Set up a WiFi network with no proxy
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set up a VPN network with a proxy
+ final int uid = Process.myUid();
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setUids(ranges);
+ LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ // Connect to VPN with proxy
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+
+ // Test that the VPN network returns a proxy, and the WiFi does not.
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+
+ // Test that the VPN network returns no proxy when it is set to null.
+ testLinkProperties.setHttpProxy(null);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set WiFi proxy and check that the vpn proxy is still null.
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Disconnect from VPN and check that the active network, which is now the WiFi, has the
+ // correct proxy setting.
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 125fe7258e94..273b8fc3773b 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
@@ -34,25 +35,23 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -194,4 +193,54 @@ public class NetworkNotificationManagerTest {
mManager.clearNotification(id);
verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
}
+
+ @Test
+ public void testSameLevelNotifications() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+ }
+
+ @Test
+ public void testClearNotificationByType() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ // clearNotification(int id, NotificationType notifyType) will check if given type is equal
+ // to previous type or not. If they are equal then clear the notification; if they are not
+ // equal then return.
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
+ // should be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ verify(mNotificationManager, times(1))
+ .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(2))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // LOST_INTERNET notification popup after LOGGED_IN notification.
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+
+ // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
+ // shouldn't be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ // LOST_INTERNET shouldn't be cleared.
+ verify(mNotificationManager, never())
+ .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
+ }
}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index f2ecef95b599..e57433a52cca 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -202,9 +202,11 @@ public class IpMemoryStoreServiceTest {
final CountDownLatch latch = new CountDownLatch(1);
functor.accept(latch);
try {
- latch.await(5000, TimeUnit.MILLISECONDS);
+ if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+ fail(timeoutMessage);
+ }
} catch (InterruptedException e) {
- fail(timeoutMessage);
+ fail("Thread was interrupted");
}
}
@@ -314,6 +316,7 @@ public class IpMemoryStoreServiceTest {
assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
assertNull(key);
assertNull(attr);
+ latch.countDown();
})));
}
@@ -383,6 +386,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(FAKE_KEYS[5], key);
+ latch.countDown();
})));
// MTU matches key 4 but v4 address matches key 5. The latter is stronger.
@@ -392,6 +396,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(FAKE_KEYS[5], key);
+ latch.countDown();
})));
// Closest to key 3 (indeed, identical)
@@ -402,6 +407,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(FAKE_KEYS[3], key);
+ latch.countDown();
})));
// Group hint alone must not be strong enough to override the rest
@@ -411,6 +417,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(FAKE_KEYS[3], key);
+ latch.countDown();
})));
// Still closest to key 3, though confidence is lower
@@ -421,6 +428,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(FAKE_KEYS[3], key);
+ latch.countDown();
})));
// But changing the MTU makes this closer to key 4
@@ -430,6 +438,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(FAKE_KEYS[4], key);
+ latch.countDown();
})));
// MTU alone not strong enough to make this group-close
@@ -441,6 +450,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertNull(key);
+ latch.countDown();
})));
}
@@ -450,6 +460,7 @@ public class IpMemoryStoreServiceTest {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
assertEquals(sameness, answer.getNetworkSameness());
+ latch.countDown();
})));
}
@@ -488,6 +499,7 @@ public class IpMemoryStoreServiceTest {
+ status.resultCode, status.isSuccess());
assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
assertNull(answer);
+ latch.countDown();
})));
}
}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7783e108f674..8f752871355f 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -182,7 +182,8 @@ cc_test_host {
defaults: ["aapt2_defaults"],
data: [
"integration-tests/CompileTest/**/*",
- "integration-tests/CommandTests/**/*"
+ "integration-tests/CommandTests/**/*",
+ "integration-tests/ConvertTest/**/*"
],
}
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 85f90806752f..7a74ba925ba0 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -284,6 +284,8 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer
// The table might be modified by below code.
auto converted_table = apk->GetResourceTable();
+ std::unordered_set<std::string> files_written;
+
// Resources
for (const auto& package : converted_table->packages) {
for (const auto& type : package->types) {
@@ -297,10 +299,14 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer
return 1;
}
- if (!serializer->SerializeFile(file, output_writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize file " << *file->path);
- return 1;
+ // Only serialize if we haven't seen this file before
+ if (files_written.insert(*file->path).second) {
+ if (!serializer->SerializeFile(file, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize file "
+ << *file->path);
+ return 1;
+ }
}
} // file
} // config_value
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 8da5bb8d5dd6..3c0fe370c516 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -18,6 +18,7 @@
#include "LoadedApk.h"
#include "test/Test.h"
+#include "ziparchive/zip_archive.h"
using testing::Eq;
using testing::Ne;
@@ -103,4 +104,45 @@ TEST_F(ConvertTest, KeepRawXmlStrings) {
EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007"));
}
+TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
+ StdErrDiagnostics diag;
+ const std::string apk_path =
+ file::BuildPath({android::base::GetExecutableDirectory(),
+ "integration-tests", "ConvertTest", "duplicate_entries.apk"});
+
+ const std::string out_convert_apk = GetTestPath("out_convert.apk");
+ std::vector<android::StringPiece> convert_args = {
+ "-o", out_convert_apk,
+ "--output-format", "proto",
+ apk_path
+ };
+ ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0));
+
+ ZipArchiveHandle handle;
+ ASSERT_THAT(OpenArchive(out_convert_apk.c_str(), &handle), Eq(0));
+
+ void* cookie = nullptr;
+
+ ZipString prefix("res/theme/10");
+ int32_t result = StartIteration(handle, &cookie, &prefix, nullptr);
+
+ // If this is -5, that means we've found a duplicate entry and this test has failed
+ EXPECT_THAT(result, Eq(0));
+
+ // But if read succeeds, verify only one res/theme/10 entry
+ int count = 0;
+
+ // Can't pass nullptrs into Next()
+ ZipString zip_name;
+ ZipEntry zip_data;
+
+ while ((result = Next(cookie, &zip_data, &zip_name)) == 0) {
+ count++;
+ }
+
+ EndIteration(cookie);
+
+ EXPECT_THAT(count, Eq(1));
+}
+
} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
new file mode 100644
index 000000000000..c558a334b369
--- /dev/null
+++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
Binary files differ
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
index 961b47cdfecd..cee0cd52a546 100644
--- a/tools/bit/aapt.cpp
+++ b/tools/bit/aapt.cpp
@@ -159,10 +159,11 @@ int
inspect_apk(Apk* apk, const string& filename)
{
// Load the manifest xml
- Command cmd("aapt");
+ Command cmd("aapt2");
cmd.AddArg("dump");
cmd.AddArg("xmltree");
cmd.AddArg(filename);
+ cmd.AddArg("--file");
cmd.AddArg("AndroidManifest.xml");
int err;
@@ -217,11 +218,11 @@ inspect_apk(Apk* apk, const string& filename)
if (current != NULL) {
Attribute attr;
string str = match[2];
- size_t colon = str.find(':');
+ size_t colon = str.rfind(':');
if (colon == string::npos) {
attr.name = str;
} else {
- attr.ns = scope->namespaces[string(str, 0, colon)];
+ attr.ns.assign(str, 0, colon);
attr.name.assign(str, colon+1, string::npos);
}
attr.value = match[3];
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index a71cea1c44f9..860094aef1f4 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -623,12 +623,13 @@ run_phases(vector<Target*> targets, const Options& options)
const string buildProduct = get_required_env("TARGET_PRODUCT", false);
const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
-
+ const string buildOut = get_out_dir();
chdir_or_exit(buildTop.c_str());
- const string buildDevice = get_build_var("TARGET_DEVICE", false);
- const string buildId = get_build_var("BUILD_ID", false);
- const string buildOut = get_out_dir();
+ BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType);
+
+ const string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false);
+ const string buildId = buildVars.GetBuildVar("BUILD_ID", false);
// Get the modules for the targets
map<string,Module> modules;
@@ -661,6 +662,7 @@ run_phases(vector<Target*> targets, const Options& options)
string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
bool syncSystem = false;
bool alwaysSyncSystem = false;
+ vector<string> systemFiles;
vector<InstallApk> installApks;
for (size_t i=0; i<targets.size(); i++) {
Target* target = targets[i];
@@ -670,6 +672,7 @@ run_phases(vector<Target*> targets, const Options& options)
// System partition
if (starts_with(file, systemPath)) {
syncSystem = true;
+ systemFiles.push_back(file);
if (!target->build) {
// If a system partition target didn't get built then
// it won't change we will always need to do adb sync
@@ -692,6 +695,19 @@ run_phases(vector<Target*> targets, const Options& options)
get_directory_contents(systemPath, &systemFilesBefore);
}
+ if (systemFiles.size() > 0){
+ print_info("System files:");
+ for (size_t i=0; i<systemFiles.size(); i++) {
+ printf(" %s\n", systemFiles[i].c_str());
+ }
+ }
+ if (installApks.size() > 0){
+ print_info("APKs to install:");
+ for (size_t i=0; i<installApks.size(); i++) {
+ printf(" %s\n", installApks[i].file.filename.c_str());
+ }
+ }
+
//
// Build
//
@@ -798,7 +814,8 @@ run_phases(vector<Target*> targets, const Options& options)
for (size_t j=0; j<target->module.installed.size(); j++) {
string filename = target->module.installed[j];
- if (!ends_with(filename, ".apk")) {
+ // Apk in the data partition
+ if (!starts_with(filename, dataPath) || !ends_with(filename, ".apk")) {
continue;
}
@@ -1004,13 +1021,16 @@ run_phases(vector<Target*> targets, const Options& options)
void
run_tab_completion(const string& word)
{
- const string buildTop = get_required_env("ANDROID_BUILD_TOP", true);
+ const string buildTop = get_required_env("ANDROID_BUILD_TOP", false);
const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+ const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
+ const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
const string buildOut = get_out_dir();
-
chdir_or_exit(buildTop.c_str());
- string buildDevice = sniff_device_name(buildOut, buildProduct);
+ BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType);
+
+ string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false);
map<string,Module> modules;
read_modules(buildOut, buildDevice, &modules, true);
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index 5a9ab22719cb..627091321b2e 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -21,6 +21,7 @@
#include "util.h"
#include <json/reader.h>
+#include <json/writer.h>
#include <json/value.h>
#include <fstream>
@@ -34,22 +35,118 @@
using namespace std;
-map<string,string> g_buildVars;
+static bool
+map_contains(const map<string,string>& m, const string& k, const string& v) {
+ map<string,string>::const_iterator it = m.find(k);
+ if (it == m.end()) {
+ return false;
+ }
+ return it->second == v;
+}
+
+static string
+make_cache_filename(const string& outDir)
+{
+ string filename(outDir);
+ return filename + "/.bit_cache";
+}
+
+BuildVars::BuildVars(const string& outDir, const string& buildProduct,
+ const string& buildVariant, const string& buildType)
+ :m_filename(),
+ m_cache()
+{
+ m_cache["TARGET_PRODUCT"] = buildProduct;
+ m_cache["TARGET_BUILD_VARIANT"] = buildVariant;
+ m_cache["TARGET_BUILD_TYPE"] = buildType;
+
+ // If we have any problems reading the file, that's ok, just do
+ // uncached calls to make / soong.
+
+ if (outDir == "") {
+ return;
+ }
+
+
+ m_filename = make_cache_filename(outDir);
+
+ std::ifstream stream(m_filename, std::ifstream::binary);
+
+ if (stream.fail()) {
+ return;
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (!reader.parse(stream, json)) {
+ return;
+ }
+
+ if (!json.isObject()) {
+ return;
+ }
+
+ map<string,string> cache;
+
+ vector<string> names = json.getMemberNames();
+ const int N = names.size();
+ for (int i=0; i<N; i++) {
+ const string& name = names[i];
+ const Json::Value& value = json[name];
+ if (!value.isString()) {
+ continue;
+ }
+ cache[name] = value.asString();
+ }
+
+ // If all of the base variables match, then we can use this cache. Otherwise, use our
+ // base one. The next time someone reads a value, the new one, with our base varaibles
+ // will be saved.
+ if (map_contains(cache, "TARGET_PRODUCT", buildProduct)
+ && map_contains(cache, "TARGET_BUILD_VARIANT", buildVariant)
+ && map_contains(cache, "TARGET_BUILD_TYPE", buildType)) {
+ m_cache = cache;
+ }
+}
+
+BuildVars::~BuildVars()
+{
+}
+
+void
+BuildVars::save()
+{
+ if (m_filename == "") {
+ return;
+ }
+
+ Json::StyledStreamWriter writer(" ");
+
+ Json::Value json(Json::objectValue);
+
+ for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) {
+ json[it->first] = it->second;
+ }
+
+ std::ofstream stream(m_filename, std::ofstream::binary);
+ writer.write(stream, json);
+}
string
-get_build_var(const string& name, bool quiet)
+BuildVars::GetBuildVar(const string& name, bool quiet)
{
int err;
- map<string,string>::iterator it = g_buildVars.find(name);
- if (it == g_buildVars.end()) {
+ map<string,string>::iterator it = m_cache.find(name);
+ if (it == m_cache.end()) {
Command cmd("build/soong/soong_ui.bash");
cmd.AddArg("--dumpvar-mode");
cmd.AddArg(name);
string output = trim(get_command_output(cmd, &err, quiet));
if (err == 0) {
- g_buildVars[name] = output;
+ m_cache[name] = output;
+ save();
return output;
} else {
return string();
@@ -59,38 +156,6 @@ get_build_var(const string& name, bool quiet)
}
}
-string
-sniff_device_name(const string& buildOut, const string& product)
-{
- string match("ro.build.product=" + product);
-
- string base(buildOut + "/target/product");
- DIR* dir = opendir(base.c_str());
- if (dir == NULL) {
- return string();
- }
-
- dirent* entry;
- while ((entry = readdir(dir)) != NULL) {
- if (entry->d_name[0] == '.') {
- continue;
- }
- if (entry->d_type == DT_DIR) {
- string filename(base + "/" + entry->d_name + "/system/build.prop");
- vector<string> lines;
- split_lines(&lines, read_file(filename));
- for (size_t i=0; i<lines.size(); i++) {
- if (lines[i] == match) {
- return entry->d_name;
- }
- }
- }
- }
-
- closedir(dir);
- return string();
-}
-
void
json_error(const string& filename, const char* error, bool quiet)
{
diff --git a/tools/bit/make.h b/tools/bit/make.h
index 1c9504d62d46..db0b69f88a0e 100644
--- a/tools/bit/make.h
+++ b/tools/bit/make.h
@@ -31,16 +31,26 @@ struct Module
vector<string> installed;
};
-string get_build_var(const string& name, bool quiet);
-
/**
- * Poke around in the out directory and try to find a device name that matches
- * our product. This is faster than running get_build_var and good enough for
- * tab completion.
- *
- * Returns the empty string if we can't find one.
+ * Class to encapsulate getting build variables. Caches the
+ * results if possible.
*/
-string sniff_device_name(const string& buildOut, const string& product);
+class BuildVars
+{
+public:
+ BuildVars(const string& outDir, const string& buildProduct,
+ const string& buildVariant, const string& buildType);
+ ~BuildVars();
+
+ string GetBuildVar(const string& name, bool quiet);
+
+private:
+ void save();
+
+ string m_filename;
+
+ map<string,string> m_cache;
+};
void read_modules(const string& buildOut, const string& buildDevice,
map<string,Module>* modules, bool quiet);
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
index 790e0b4b227e..35feda11ec29 100644
--- a/tools/bit/print.cpp
+++ b/tools/bit/print.cpp
@@ -116,6 +116,20 @@ print_warning(const char* format, ...)
}
void
+print_info(const char* format, ...)
+{
+ fputs(g_escapeBold, stdout);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+
+ fputs(g_escapeEndColor, stdout);
+ fputc('\n', stdout);
+}
+
+void
print_one_line(const char* format, ...)
{
if (g_stdoutIsTty) {
diff --git a/tools/bit/print.h b/tools/bit/print.h
index b6c3e9aa27fa..db6cf5f65cf8 100644
--- a/tools/bit/print.h
+++ b/tools/bit/print.h
@@ -33,6 +33,7 @@ void print_status(const char* format, ...);
void print_command(const Command& command);
void print_error(const char* format, ...);
void print_warning(const char* format, ...);
+void print_info(const char* format, ...);
void print_one_line(const char* format, ...);
void check_error(int err);
diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp
index 9b5df56e3987..06ff05e5d755 100644
--- a/tools/processors/view_inspector/Android.bp
+++ b/tools/processors/view_inspector/Android.bp
@@ -1,6 +1,8 @@
-java_library_host {
+java_plugin {
name: "view-inspector-annotation-processor",
+ processor_class: "android.processor.view.inspector.PlatformInspectableProcessor",
+
srcs: ["src/java/**/*.java"],
java_resource_dirs: ["src/resources"],
diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem
index 0af577bf81e1..17a1dff71707 100644
--- a/tools/signedconfig/debug_key.pem
+++ b/tools/signedconfig/debug_key.pem
@@ -1,5 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
-MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49
-AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8
-SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MHcCAQEEIFbNNr1/TsFlvnmH1z6e0xyact9t7PDs+VFWc7QFtoRcoAoGCCqGSM49
+AwEHoUQDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1x7A8PV1
+ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
-----END EC PRIVATE KEY-----
diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem
index f61f81322b94..d9f0d387b823 100644
--- a/tools/signedconfig/debug_public.pem
+++ b/tools/signedconfig/debug_public.pem
@@ -1,4 +1,4 @@
-----BEGIN PUBLIC KEY-----
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE
-CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13Yr
+U1haIhVC5296InRu1x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
-----END PUBLIC KEY-----
diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh
index 28e54289f8f8..3a2814a62aff 100755
--- a/tools/signedconfig/debug_sign.sh
+++ b/tools/signedconfig/debug_sign.sh
@@ -2,5 +2,5 @@
# Script to sign data with the debug keys. Outputs base64 for embedding into
# APK metadata.
-openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem $1 | base64 -w 0
+openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem <(echo -n "$1" | base64 -d) | base64 -w 0
echo
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index a0ce9dde3068..d5497990aefd 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -214,4 +214,6 @@ interface IWifiManager
in IDppCallback callback);
void stopDppSession();
+
+ void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec);
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index d2d711f10944..96493de69673 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -787,6 +787,18 @@ public class WifiConfiguration implements Parcelable {
public boolean trusted;
/**
+ * This Wifi configuration is created from a {@link WifiNetworkSuggestion}
+ * @hide
+ */
+ public boolean fromWifiNetworkSuggestion;
+
+ /**
+ * This Wifi configuration is created from a {@link WifiNetworkSpecifier}
+ * @hide
+ */
+ public boolean fromWifiNetworkSpecifier;
+
+ /**
* Indicates if the creator of this configuration has expressed that it
* should be considered metered.
*
@@ -1668,6 +1680,8 @@ public class WifiConfiguration implements Parcelable {
ephemeral = false;
osu = false;
trusted = true; // Networks are considered trusted by default.
+ fromWifiNetworkSuggestion = false;
+ fromWifiNetworkSpecifier = false;
meteredHint = false;
meteredOverride = METERED_OVERRIDE_NONE;
useExternalScores = false;
@@ -1779,10 +1793,13 @@ public class WifiConfiguration implements Parcelable {
if (this.ephemeral) sbuf.append(" ephemeral");
if (this.osu) sbuf.append(" osu");
if (this.trusted) sbuf.append(" trusted");
+ if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
+ if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
if (this.meteredHint) sbuf.append(" meteredHint");
if (this.useExternalScores) sbuf.append(" useExternalScores");
if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
- || this.ephemeral || this.trusted || this.meteredHint || this.useExternalScores) {
+ || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion
+ || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) {
sbuf.append("\n");
}
if (this.meteredOverride != METERED_OVERRIDE_NONE) {
@@ -2270,6 +2287,8 @@ public class WifiConfiguration implements Parcelable {
ephemeral = source.ephemeral;
osu = source.osu;
trusted = source.trusted;
+ fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
+ fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
meteredHint = source.meteredHint;
meteredOverride = source.meteredOverride;
useExternalScores = source.useExternalScores;
@@ -2347,6 +2366,8 @@ public class WifiConfiguration implements Parcelable {
dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
dest.writeInt(ephemeral ? 1 : 0);
dest.writeInt(trusted ? 1 : 0);
+ dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
+ dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
dest.writeInt(meteredHint ? 1 : 0);
dest.writeInt(meteredOverride);
dest.writeInt(useExternalScores ? 1 : 0);
@@ -2418,6 +2439,8 @@ public class WifiConfiguration implements Parcelable {
config.isLegacyPasspointConfig = in.readInt() != 0;
config.ephemeral = in.readInt() != 0;
config.trusted = in.readInt() != 0;
+ config.fromWifiNetworkSuggestion = in.readInt() != 0;
+ config.fromWifiNetworkSpecifier = in.readInt() != 0;
config.meteredHint = in.readInt() != 0;
config.meteredOverride = in.readInt();
config.useExternalScores = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 35fba3dcf7cf..488de8789178 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.net.NetworkInfo.DetailedState;
@@ -120,8 +121,14 @@ public class WifiInfo implements Parcelable {
@UnsupportedAppUsage
private String mMacAddress = DEFAULT_MAC_ADDRESS;
+ /**
+ * Whether the network is ephemeral or not.
+ */
private boolean mEphemeral;
+ /**
+ * Whether the network is trusted or not.
+ */
private boolean mTrusted;
/**
@@ -130,6 +137,12 @@ public class WifiInfo implements Parcelable {
private boolean mOsuAp;
/**
+ * If connected to a network suggestion or specifier, store the package name of the app,
+ * else null.
+ */
+ private String mNetworkSuggestionOrSpecifierPackageName;
+
+ /**
* Running total count of lost (not ACKed) transmitted unicast data packets.
* @hide
*/
@@ -209,6 +222,7 @@ public class WifiInfo implements Parcelable {
setMeteredHint(false);
setEphemeral(false);
setOsuAp(false);
+ setNetworkSuggestionOrSpecifierPackageName(null);
txBad = 0;
txSuccess = 0;
rxSuccess = 0;
@@ -240,6 +254,8 @@ public class WifiInfo implements Parcelable {
mMeteredHint = source.mMeteredHint;
mEphemeral = source.mEphemeral;
mTrusted = source.mTrusted;
+ mNetworkSuggestionOrSpecifierPackageName =
+ source.mNetworkSuggestionOrSpecifierPackageName;
mOsuAp = source.mOsuAp;
txBad = source.txBad;
txRetries = source.txRetries;
@@ -476,6 +492,17 @@ public class WifiInfo implements Parcelable {
return mOsuAp;
}
+ /** {@hide} */
+ public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) {
+ mNetworkSuggestionOrSpecifierPackageName = packageName;
+ }
+
+ /** {@hide} */
+ public @Nullable String getNetworkSuggestionOrSpecifierPackageName() {
+ return mNetworkSuggestionOrSpecifierPackageName;
+ }
+
+
/** @hide */
@UnsupportedAppUsage
public void setNetworkId(int id) {
@@ -634,6 +661,7 @@ public class WifiInfo implements Parcelable {
dest.writeDouble(rxSuccessRate);
mSupplicantState.writeToParcel(dest, flags);
dest.writeInt(mOsuAp ? 1 : 0);
+ dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
}
/** Implement the Parcelable interface {@hide} */
@@ -672,6 +700,7 @@ public class WifiInfo implements Parcelable {
info.rxSuccessRate = in.readDouble();
info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
info.mOsuAp = in.readInt() != 0;
+ info.mNetworkSuggestionOrSpecifierPackageName = in.readString();
return info;
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 084bd0936725..066823931832 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -16,7 +16,7 @@
package android.net.wifi;
-import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.READ_WIFI_CREDENTIAL;
@@ -950,8 +950,7 @@ public class WifiManager {
* which was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()} flag
* set.
* <p>
- * Note: The broadcast is sent to the app only if it holds either one of
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+ * Note: The broadcast is sent to the app only if it holds
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
*
* @see #EXTRA_NETWORK_SUGGESTION
@@ -1183,7 +1182,7 @@ public class WifiManager {
* containing configurations which they created.
*/
@Deprecated
- @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE})
+ @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
public List<WifiConfiguration> getConfiguredNetworks() {
try {
ParceledListSlice<WifiConfiguration> parceledList =
@@ -1199,7 +1198,7 @@ public class WifiManager {
/** @hide */
@SystemApi
- @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
+ @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE, READ_WIFI_CREDENTIAL})
public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
try {
ParceledListSlice<WifiConfiguration> parceledList =
@@ -1405,7 +1404,6 @@ public class WifiManager {
* {@link #reject()} to return the user's selection back to the platform via this callback.
* @hide
*/
- @SystemApi
public interface NetworkRequestUserSelectionCallback {
/**
* User selected this network to connect to.
@@ -1429,7 +1427,6 @@ public class WifiManager {
* or reject the request by the app.
* @hide
*/
- @SystemApi
public interface NetworkRequestMatchCallback {
/**
* Invoked to register a callback to be invoked to convey user selection. The callback
@@ -1606,7 +1603,6 @@ public class WifiManager {
* object. If null, then the application's main thread will be used.
* @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback,
@Nullable Handler handler) {
@@ -1636,7 +1632,6 @@ public class WifiManager {
* @param callback Callback for network match events
* @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void unregisterNetworkRequestMatchCallback(
@NonNull NetworkRequestMatchCallback callback) {
@@ -1656,8 +1651,7 @@ public class WifiManager {
* When the device decides to connect to one of the provided network suggestions, platform sends
* a directed broadcast {@link #ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the app if
* the network was created with {@link WifiNetworkConfigBuilder#setIsAppInteractionRequired()}
- * flag set and the app holds either one of
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+ * flag set and the app holds
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission.
*<p>
* NOTE:
@@ -2290,7 +2284,6 @@ public class WifiManager {
/**
* Return the results of the latest access point scan.
* @return the list of access points found in the most recent scan. An app must hold
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
* in order to get valid results.
*/
@@ -2601,7 +2594,7 @@ public class WifiManager {
* <p>
* Applications need to have the following permissions to start LocalOnlyHotspot: {@link
* android.Manifest.permission#CHANGE_WIFI_STATE} and {@link
- * android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}. Callers without
+ * android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. Callers without
* the permissions will trigger a {@link java.lang.SecurityException}.
* <p>
* @param callback LocalOnlyHotspotCallback for the application to receive updates about
@@ -2684,7 +2677,7 @@ public class WifiManager {
* {@link LocalOnlyHotspotObserver#onStopped()} callbacks.
* <p>
* Applications should have the
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}
* permission. Callers without the permission will trigger a
* {@link java.lang.SecurityException}.
* <p>
@@ -4865,4 +4858,26 @@ public class WifiManager {
throw e.rethrowFromSystemServer();
}
}
-}
+
+ /**
+ * Provide a Wi-Fi usability score information to be recorded (but not acted upon) by the
+ * framework. The Wi-Fi usability score is derived from {@link WifiUsabilityStatsListener}
+ * where a score is matched to Wi-Fi usability statistics using the sequence number. The score
+ * is used to quantify whether Wi-Fi is usable in a future time.
+ *
+ * @param seqNum Sequence number of the Wi-Fi usability score.
+ * @param score The Wi-Fi usability score.
+ * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+ try {
+ mService.updateWifiUsabilityScore(seqNum, score, predictionHorizonSec);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+} \ No newline at end of file
diff --git a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
index 81a06e825439..a8b19b3e2f64 100644
--- a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
+++ b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
@@ -30,7 +30,7 @@ package android.net.wifi.aware;
public class IdentityChangedListener {
/**
* @param mac The MAC address of the Aware discovery interface. The application must have the
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address,
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} to get the actual MAC address,
* otherwise all 0's will be provided.
*/
public void onIdentityChanged(byte[] mac) {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 1fa1fd521a8e..8aef7a2cc6b6 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -231,7 +231,7 @@ public class WifiAwareManager {
* <p>
* This version of the API attaches a listener to receive the MAC address of the Aware interface
* on startup and whenever it is updated (it is randomized at regular intervals for privacy).
- * The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
* permission to execute this attach request. Otherwise, use the
* {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
* requirements this listener will wake up the host at regular intervals causing higher power
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 5f8841cb0148..245b3043d30a 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -133,7 +133,7 @@ public class WifiAwareSession implements AutoCloseable {
* An application must use the {@link DiscoverySession#close()} to
* terminate the publish discovery session once it isn't needed. This will free
* resources as well terminate any on-air transmissions.
- * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
* permission to start a publish discovery session.
*
* @param publishConfig The {@link PublishConfig} specifying the
@@ -179,7 +179,7 @@ public class WifiAwareSession implements AutoCloseable {
* An application must use the {@link DiscoverySession#close()} to
* terminate the subscribe discovery session once it isn't needed. This will free
* resources as well terminate any on-air transmissions.
- * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+ * <p>The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
* permission to start a subscribe discovery session.
*
* @param subscribeConfig The {@link SubscribeConfig} specifying the
diff --git a/wifi/java/android/net/wifi/aware/package.html b/wifi/java/android/net/wifi/aware/package.html
index d5d962f629de..c4f2e1fec96b 100644
--- a/wifi/java/android/net/wifi/aware/package.html
+++ b/wifi/java/android/net/wifi/aware/package.html
@@ -15,7 +15,7 @@
<ul>
<li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
<li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
- <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+ <li>{@link android.Manifest.permission#ACCESS_FINE_LOCATION}</li>
</ul>
<p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi Aware
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 1bed914c7772..052ab99da905 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -1139,7 +1139,7 @@ public class WifiP2pManager {
* @param c is the channel created at {@link #initialize}
* @param listener for callbacks on success or failure. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void discoverPeers(Channel c, ActionListener listener) {
checkChannel(c);
c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
@@ -1183,7 +1183,7 @@ public class WifiP2pManager {
* @param config options as described in {@link WifiP2pConfig} class
* @param listener for callbacks on success or failure. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
checkChannel(c);
checkP2pConfig(config);
@@ -1225,7 +1225,7 @@ public class WifiP2pManager {
* @param c is the channel created at {@link #initialize}
* @param listener for callbacks on success or failure. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void createGroup(Channel c, ActionListener listener) {
checkChannel(c);
c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID,
@@ -1256,7 +1256,7 @@ public class WifiP2pManager {
* @param config the configuration of a p2p group.
* @param listener for callbacks on success or failure. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void createGroup(@NonNull Channel c,
@Nullable WifiP2pConfig config,
@Nullable ActionListener listener) {
@@ -1344,7 +1344,7 @@ public class WifiP2pManager {
* @param servInfo is a local service information.
* @param listener for callbacks on success or failure. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
checkChannel(c);
checkServiceInfo(servInfo);
@@ -1454,7 +1454,7 @@ public class WifiP2pManager {
* @param c is the channel created at {@link #initialize}
* @param listener for callbacks on success or failure. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void discoverServices(Channel c, ActionListener listener) {
checkChannel(c);
c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
@@ -1530,7 +1530,7 @@ public class WifiP2pManager {
* @param c is the channel created at {@link #initialize}
* @param listener for callback when peer list is available. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void requestPeers(Channel c, PeerListListener listener) {
checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
@@ -1553,7 +1553,7 @@ public class WifiP2pManager {
* @param c is the channel created at {@link #initialize}
* @param listener for callback when group info is available. Can be null.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
public void requestGroupInfo(Channel c, GroupInfoListener listener) {
checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index 226c7c48a3b3..c236c7a05488 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -476,4 +476,9 @@ public class BaseWifiService extends IWifiManager.Stub {
public void removeWifiUsabilityStatsListener(int listenerIdentifier) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 7bff68aaaa97..449423f44a35 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -60,6 +60,8 @@ public class WifiConfigurationTest {
config.setPasspointManagementObjectTree(cookie);
config.trusted = false;
config.updateIdentifier = "1234";
+ config.fromWifiNetworkSpecifier = true;
+ config.fromWifiNetworkSuggestion = true;
MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -76,6 +78,8 @@ public class WifiConfigurationTest {
assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
assertEquals(config.updateIdentifier, reconfig.updateIdentifier);
assertFalse(reconfig.trusted);
+ assertTrue(config.fromWifiNetworkSpecifier);
+ assertTrue(config.fromWifiNetworkSuggestion);
Parcel parcelWW = Parcel.obtain();
reconfig.writeToParcel(parcelWW, 0);
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 677bf371c781..948dcfa47f59 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -35,6 +35,7 @@ public class WifiInfoTest {
private static final long TEST_TX_RETRIES = 2;
private static final long TEST_TX_BAD = 3;
private static final long TEST_RX_SUCCESS = 4;
+ private static final String TEST_PACKAGE_NAME = "com.test.example";
/**
* Verify parcel write/read with WifiInfo.
@@ -48,6 +49,7 @@ public class WifiInfoTest {
writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
writeWifiInfo.setTrusted(true);
writeWifiInfo.setOsuAp(true);
+ writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME);
Parcel parcel = Parcel.obtain();
writeWifiInfo.writeToParcel(parcel, 0);
@@ -62,5 +64,6 @@ public class WifiInfoTest {
assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
assertTrue(readWifiInfo.isTrusted());
assertTrue(readWifiInfo.isOsuAp());
+ assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName());
}
}