summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt56
-rw-r--r--api/system-current.txt2
-rw-r--r--api/test-current.txt3
-rw-r--r--cmds/statsd/src/atoms.proto3
-rw-r--r--core/java/android/accessibilityservice/AccessibilityGestureInfo.java3
-rw-r--r--core/java/android/app/AppOpsManager.java1052
-rw-r--r--core/java/android/app/AsyncNotedAppOp.aidl (renamed from core/tests/coretests/src/android/app/activity/LocalDeniedService.java)9
-rw-r--r--core/java/android/app/AsyncNotedAppOp.java245
-rw-r--r--core/java/android/app/ITaskStackListener.aidl7
-rw-r--r--core/java/android/app/Notification.java34
-rw-r--r--core/java/android/app/SharedPreferencesImpl.java98
-rw-r--r--core/java/android/app/StatusBarManager.java1
-rw-r--r--core/java/android/app/SyncNotedAppOp.java68
-rw-r--r--core/java/android/app/TaskStackListener.java4
-rw-r--r--core/java/android/app/Vr2dDisplayProperties.java4
-rw-r--r--core/java/android/app/backup/RestoreDescription.java2
-rw-r--r--core/java/android/app/prediction/AppPredictionContext.java2
-rw-r--r--core/java/android/app/prediction/AppPredictionSessionId.java3
-rw-r--r--core/java/android/app/prediction/AppTarget.java2
-rw-r--r--core/java/android/app/prediction/AppTargetEvent.java2
-rw-r--r--core/java/android/app/prediction/AppTargetId.java3
-rw-r--r--core/java/android/app/usage/CacheQuotaHint.java2
-rw-r--r--core/java/android/app/usage/UsageEvents.java1
-rw-r--r--core/java/android/content/PermissionChecker.java53
-rw-r--r--core/java/android/content/SharedPreferences.java65
-rw-r--r--core/java/android/content/om/OverlayInfo.java3
-rw-r--r--core/java/android/content/pm/SuspendDialogInfo.java3
-rw-r--r--core/java/android/hardware/display/AmbientBrightnessDayStats.java4
-rw-r--r--core/java/android/hardware/display/BrightnessConfiguration.java3
-rw-r--r--core/java/android/hardware/display/BrightnessCorrection.java5
-rw-r--r--core/java/android/hardware/hdmi/HdmiDeviceInfo.java5
-rw-r--r--core/java/android/hardware/hdmi/HdmiPortInfo.java5
-rw-r--r--core/java/android/hardware/location/ContextHubInfo.java2
-rw-r--r--core/java/android/hardware/location/ContextHubIntentEvent.java1
-rw-r--r--core/java/android/hardware/location/ContextHubMessage.java4
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java4
-rw-r--r--core/java/android/hardware/location/MemoryRegion.java2
-rw-r--r--core/java/android/hardware/location/NanoApp.java2
-rw-r--r--core/java/android/hardware/location/NanoAppFilter.java2
-rw-r--r--core/java/android/hardware/location/NanoAppInstanceInfo.java1
-rw-r--r--core/java/android/hardware/location/NanoAppMessage.java4
-rw-r--r--core/java/android/hardware/radio/ProgramList.java3
-rw-r--r--core/java/android/hardware/radio/ProgramSelector.java6
-rw-r--r--core/java/android/hardware/radio/RadioManager.java24
-rw-r--r--core/java/android/hardware/radio/RadioMetadata.java4
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java4
-rw-r--r--core/java/android/hardware/usb/UsbPort.java2
-rw-r--r--core/java/android/hardware/usb/UsbPortStatus.java4
-rw-r--r--core/java/android/net/INetworkPolicyListener.aidl2
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl2
-rw-r--r--core/java/android/net/IpSecManager.java1
-rw-r--r--core/java/android/net/NetworkKey.java4
-rw-r--r--core/java/android/net/NetworkPolicyManager.java3
-rw-r--r--core/java/android/net/RssiCurve.java5
-rw-r--r--core/java/android/net/ScoredNetwork.java4
-rw-r--r--core/java/android/net/StaticIpConfiguration.java3
-rw-r--r--core/java/android/net/WebAddress.java2
-rw-r--r--core/java/android/net/WifiKey.java5
-rw-r--r--core/java/android/net/apf/ApfCapabilities.java4
-rw-r--r--core/java/android/net/metrics/ApfProgramEvent.java4
-rw-r--r--core/java/android/net/metrics/ApfStats.java4
-rw-r--r--core/java/android/net/metrics/DhcpClientEvent.java4
-rw-r--r--core/java/android/net/metrics/DhcpErrorEvent.java2
-rw-r--r--core/java/android/net/metrics/IpManagerEvent.java5
-rw-r--r--core/java/android/net/metrics/IpReachabilityEvent.java5
-rw-r--r--core/java/android/net/metrics/NetworkEvent.java5
-rw-r--r--core/java/android/net/metrics/RaEvent.java4
-rw-r--r--core/java/android/net/metrics/ValidationProbeEvent.java4
-rw-r--r--core/java/android/net/util/MultinetworkPolicyTracker.java29
-rw-r--r--core/java/android/os/BatterySaverPolicyConfig.java1
-rw-r--r--core/java/android/os/Binder.java13
-rw-r--r--core/java/android/os/BinderProxy.java10
-rw-r--r--core/java/android/os/IBinder.java5
-rw-r--r--core/java/android/os/IncidentManager.java2
-rw-r--r--core/java/android/os/IncidentReportArgs.java2
-rw-r--r--core/java/android/os/Parcel.java21
-rw-r--r--core/java/android/os/ServiceManagerNative.java10
-rw-r--r--core/java/android/os/ServiceSpecificException.java2
-rw-r--r--core/java/android/os/VibrationEffect.java6
-rw-r--r--core/java/android/os/WorkSource.java6
-rw-r--r--core/java/android/permission/PermissionManager.java3
-rw-r--r--core/java/android/printservice/PrintServiceInfo.java4
-rw-r--r--core/java/android/provider/SearchIndexableData.java2
-rw-r--r--core/java/android/provider/SearchIndexableResource.java2
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/provider/TEST_MAPPING18
-rw-r--r--core/java/android/service/autofill/augmented/FillRequest.java1
-rw-r--r--core/java/android/service/autofill/augmented/PresentationParams.java1
-rw-r--r--core/java/android/service/contentcapture/ActivityEvent.java1
-rw-r--r--core/java/android/service/euicc/EuiccProfileInfo.java4
-rw-r--r--core/java/android/service/notification/Adjustment.java1
-rw-r--r--core/java/android/service/notification/NotificationStats.java5
-rw-r--r--core/java/android/service/notification/SnoozeCriterion.java3
-rw-r--r--core/java/android/service/resolver/ResolverTarget.java6
-rw-r--r--core/java/android/service/watchdog/ExplicitHealthCheckService.java3
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureEvent.java1
-rw-r--r--core/java/android/webkit/WebView.java2
-rw-r--r--core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl (renamed from core/tests/coretests/src/android/app/activity/LocalGrantedService.java)12
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl10
-rw-r--r--core/java/com/android/internal/util/AnnotationValidations.java44
-rw-r--r--core/proto/android/providers/settings/global.proto1
-rw-r--r--core/tests/coretests/AndroidManifest.xml18
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalReceiver.java4
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalService.java122
-rw-r--r--core/tests/coretests/src/android/app/activity/MetaDataTest.java2
-rw-r--r--core/tests/coretests/src/android/app/activity/ServiceTest.java595
-rw-r--r--core/tests/coretests/src/android/os/ProcessTest.java4
-rw-r--r--libs/androidfw/ResourceTypes.cpp4
-rw-r--r--location/java/android/location/GnssCapabilities.java2
-rw-r--r--location/java/android/location/GnssMeasurementCorrections.java1
-rw-r--r--location/java/android/location/GnssReflectingPlane.java1
-rw-r--r--location/java/android/location/GnssSingleSatCorrection.java1
-rw-r--r--location/java/android/location/GpsClock.java2
-rw-r--r--location/java/android/location/GpsMeasurement.java2
-rw-r--r--location/java/android/location/GpsMeasurementsEvent.java1
-rw-r--r--location/java/android/location/GpsNavigationMessage.java1
-rw-r--r--location/java/android/location/GpsNavigationMessageEvent.java1
-rw-r--r--location/java/android/location/LocationRequest.java1
-rw-r--r--media/java/android/media/AudioFocusInfo.java3
-rw-r--r--media/java/android/media/audiopolicy/AudioProductStrategy.java1
-rw-r--r--media/java/android/media/session/MediaSessionManager.java2
-rw-r--r--media/java/android/media/tv/TvInputHardwareInfo.java3
-rw-r--r--media/java/android/media/tv/TvStreamConfig.java5
-rw-r--r--media/java/android/media/tv/TvTrackInfo.java1
-rw-r--r--media/jni/android_media_MediaCodec.cpp8
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java8
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java11
-rw-r--r--packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-hi/arrays.xml16
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-hy/arrays.xml2
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java19
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java5
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java39
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java14
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java5
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/docs/physics-animation-layout.md40
-rw-r--r--packages/SystemUI/res-keyguard/values-cs/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-fa/strings.xml2
-rw-r--r--packages/SystemUI/res/drawable/bubble_dismiss_circle.xml1
-rw-r--r--packages/SystemUI/res/layout/bubble_dismiss_target.xml31
-rw-r--r--packages/SystemUI/res/layout/bubble_view.xml1
-rw-r--r--packages/SystemUI/res/layout/super_status_bar.xml10
-rw-r--r--packages/SystemUI/res/values-be/strings.xml4
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml22
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml2
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml2
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml2
-rw-r--r--packages/SystemUI/res/values-land/dimens.xml4
-rw-r--r--packages/SystemUI/res/values-or/strings.xml6
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml4
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml8
-rw-r--r--packages/SystemUI/res/values/dimens.xml32
-rw-r--r--packages/SystemUI/res/values/ids.xml1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java7
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java339
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java474
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java448
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java686
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java249
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java321
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java181
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java160
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java125
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java219
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java266
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java)20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java589
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java279
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java10
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java4
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java44
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java17
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java11
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java186
-rw-r--r--services/core/java/com/android/server/connectivity/DataConnectionStats.java16
-rw-r--r--services/core/java/com/android/server/net/LockdownVpnTracker.java34
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java32
-rw-r--r--services/core/java/com/android/server/notification/BubbleExtractor.java3
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java183
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java39
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskChangeNotificationController.java19
-rw-r--r--services/core/jni/com_android_server_power_PowerManagerService.cpp9
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java45
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java216
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java41
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java48
-rw-r--r--telecomm/java/android/telecom/AudioState.java5
-rw-r--r--telephony/java/android/telephony/CallAttributes.java4
-rw-r--r--telephony/java/android/telephony/CallQuality.java5
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java11
-rw-r--r--telephony/java/android/telephony/CarrierRestrictionRules.java1
-rw-r--r--telephony/java/android/telephony/DataSpecificRegistrationInfo.java4
-rw-r--r--telephony/java/android/telephony/LteVopsSupportInfo.java5
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java3
-rw-r--r--telephony/java/android/telephony/PhoneNumberRange.java4
-rw-r--r--telephony/java/android/telephony/PreciseCallState.java5
-rw-r--r--telephony/java/android/telephony/PreciseDataConnectionState.java3
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java7
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java93
-rw-r--r--telephony/java/android/telephony/SubscriptionPlan.java2
-rw-r--r--telephony/java/android/telephony/TelephonyHistogram.java5
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java49
-rw-r--r--telephony/java/android/telephony/UiccAccessRule.java4
-rw-r--r--telephony/java/android/telephony/UiccSlotInfo.java5
-rw-r--r--telephony/java/android/telephony/data/DataCallResponse.java3
-rw-r--r--telephony/java/android/telephony/data/DataProfile.java3
-rw-r--r--telephony/java/android/telephony/euicc/EuiccNotification.java4
-rw-r--r--telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java4
-rw-r--r--telephony/java/android/telephony/ims/ImsCallForwardInfo.java1
-rw-r--r--telephony/java/android/telephony/ims/ImsCallProfile.java2
-rw-r--r--telephony/java/android/telephony/ims/ImsConferenceState.java2
-rw-r--r--telephony/java/android/telephony/ims/ImsExternalCallState.java1
-rw-r--r--telephony/java/android/telephony/ims/ImsReasonInfo.java3
-rw-r--r--telephony/java/android/telephony/ims/ImsSsData.java2
-rw-r--r--telephony/java/android/telephony/ims/ImsSsInfo.java1
-rw-r--r--telephony/java/android/telephony/ims/ImsStreamMediaProfile.java2
-rw-r--r--telephony/java/android/telephony/ims/ImsSuppServiceNotification.java2
-rw-r--r--telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java3
-rw-r--r--telephony/java/android/telephony/ims/feature/MmTelFeature.java1
-rw-r--r--telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java5
-rw-r--r--telephony/java/android/telephony/mbms/DownloadRequest.java3
-rw-r--r--telephony/java/com/android/internal/telephony/DctConstants.java3
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java5
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java15
-rw-r--r--wifi/java/android/net/wifi/RttManager.java2
-rw-r--r--wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java4
-rw-r--r--wifi/java/android/net/wifi/hotspot2/OsuProvider.java4
-rw-r--r--wifi/java/android/net/wifi/rtt/RangingRequest.java3
-rw-r--r--wifi/java/android/net/wifi/rtt/ResponderConfig.java3
270 files changed, 6696 insertions, 3320 deletions
diff --git a/api/current.txt b/api/current.txt
index 6adddd064fb5..0a301e2824a2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2816,7 +2816,7 @@ package android.accessibilityservice {
method public int describeContents();
method public int getDisplayId();
method public int getGestureId();
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureInfo> CREATOR;
}
@@ -4279,14 +4279,21 @@ package android.app {
method public void checkPackage(int, @NonNull String);
method public void finishOp(@NonNull String, int, @NonNull String);
method public boolean isOpActive(@NonNull String, int, @NonNull String);
- method public int noteOp(@NonNull String, int, @NonNull String);
- method public int noteOpNoThrow(@NonNull String, int, @NonNull String);
- method public int noteProxyOp(@NonNull String, @NonNull String);
- method public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
- method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+ method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
+ method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String);
+ method @Deprecated public int noteOpNoThrow(@NonNull String, int, @NonNull String);
+ method public int noteOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
+ method @Deprecated public int noteProxyOp(@NonNull String, @NonNull String);
+ method public int noteProxyOp(@NonNull String, @Nullable String, int, @Nullable String);
+ method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
+ method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+ method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int, @Nullable String);
method public static String permissionToOp(String);
- method public int startOp(@NonNull String, int, @NonNull String);
- method public int startOpNoThrow(@NonNull String, int, @NonNull String);
+ method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
+ method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
+ method public int startOp(@NonNull String, int, @Nullable String, @Nullable String);
+ method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
+ method public int startOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
@@ -4338,6 +4345,14 @@ package android.app {
field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1
}
+ public abstract static class AppOpsManager.AppOpsCollector {
+ ctor public AppOpsManager.AppOpsCollector();
+ method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor();
+ method public abstract void onAsyncNoted(@NonNull android.app.AsyncNotedAppOp);
+ method public abstract void onNoted(@NonNull android.app.SyncNotedAppOp);
+ method public abstract void onSelfNoted(@NonNull android.app.SyncNotedAppOp);
+ }
+
public static interface AppOpsManager.OnOpActiveChangedListener {
method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
}
@@ -4458,6 +4473,17 @@ package android.app {
field public String serviceDetails;
}
+ public final class AsyncNotedAppOp implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getMessage();
+ method @Nullable public String getNotingPackageName();
+ method @IntRange(from=0) public int getNotingUid();
+ method @NonNull public String getOp();
+ method @IntRange(from=0) public long getTime();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
+ }
+
public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
ctor public AuthenticationRequiredException(Throwable, android.app.PendingIntent);
method public int describeContents();
@@ -6267,6 +6293,10 @@ package android.app {
public class StatusBarManager {
}
+ public final class SyncNotedAppOp {
+ method @NonNull public String getOp();
+ }
+
@Deprecated public class TabActivity extends android.app.ActivityGroup {
ctor @Deprecated public TabActivity();
method @Deprecated public android.widget.TabHost getTabHost();
@@ -10875,9 +10905,7 @@ package android.content {
method @Nullable public String getString(String, @Nullable String);
method @Nullable public java.util.Set<java.lang.String> getStringSet(String, @Nullable java.util.Set<java.lang.String>);
method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
- method public default void registerOnSharedPreferencesClearListener(@NonNull android.content.SharedPreferences.OnSharedPreferencesClearListener);
method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
- method public default void unregisterOnSharedPreferencesClearListener(@NonNull android.content.SharedPreferences.OnSharedPreferencesClearListener);
}
public static interface SharedPreferences.Editor {
@@ -10897,10 +10925,6 @@ package android.content {
method public void onSharedPreferenceChanged(android.content.SharedPreferences, String);
}
- public static interface SharedPreferences.OnSharedPreferencesClearListener {
- method public void onSharedPreferencesClear(@NonNull android.content.SharedPreferences, @NonNull java.util.Set<java.lang.String>);
- }
-
public class SyncAdapterType implements android.os.Parcelable {
ctor public SyncAdapterType(String, String, boolean, boolean);
ctor public SyncAdapterType(android.os.Parcel);
@@ -28204,7 +28228,7 @@ package android.media.tv {
method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
- method public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
@@ -44082,7 +44106,7 @@ package android.telephony {
public class CarrierConfigManager {
method @Nullable public android.os.PersistableBundle getConfig();
- method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(String, int);
+ method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
method @Nullable public android.os.PersistableBundle getConfigForSubId(int);
method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
method public void notifyConfigChangedForSubId(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index e752f7c924f6..e87007fe345b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1207,7 +1207,7 @@ package android.app.usage {
public static final class UsageEvents.Event {
method public int getInstanceId();
- method public String getNotificationChannelId();
+ method @Nullable public String getNotificationChannelId();
method @Nullable public String getTaskRootClassName();
method @Nullable public String getTaskRootPackageName();
method public boolean isInstantApp();
diff --git a/api/test-current.txt b/api/test-current.txt
index b2ed91b20dfe..16bdefa0e231 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2372,6 +2372,7 @@ package android.provider {
field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
field public static final String LOW_POWER_MODE = "low_power";
field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
+ field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
}
@@ -2395,7 +2396,7 @@ package android.provider {
field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
field public static final String NOTIFICATION_BADGING = "notification_badging";
- field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+ field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 273277953569..2c8a5562c979 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -5939,7 +5939,8 @@ message BubbleUIChanged {
optional bool is_ongoing = 10;
// Whether the bubble is produced by an app running in foreground.
- optional bool is_foreground = 11;
+ // This is deprecated and the value should be ignored.
+ optional bool is_foreground = 11 [deprecated = true];
}
/**
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java b/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
index dc50a4c6c633..28c1dea1dbe7 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
@@ -114,6 +114,7 @@ public final class AccessibilityGestureInfo implements Parcelable {
return mGestureId;
}
+ @NonNull
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureInfo[");
@@ -133,7 +134,7 @@ public final class AccessibilityGestureInfo implements Parcelable {
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeInt(mGestureId);
parcel.writeInt(mDisplayId);
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 34045c995aed..1f283eafde0c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -28,13 +28,19 @@ import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.app.usage.UsageStatsManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
+import android.database.DatabaseUtils;
import android.media.AudioAttributes.AttributeUsage;
import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
@@ -49,9 +55,12 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.ZygoteInit;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -59,10 +68,12 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -118,6 +129,12 @@ public class AppOpsManager {
private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
new ArrayMap<>();
+ private static final Object sLock = new Object();
+
+ /** Current {@link AppOpsCollector}. Change via {@link #setNotedAppOpsCollector} */
+ @GuardedBy("sLock")
+ private static @Nullable AppOpsCollector sNotedAppOpsCollector;
+
static IBinder sToken;
/** @hide */
@@ -1126,6 +1143,22 @@ public class AppOpsManager {
/** @hide Query all packages on device */
public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
+
+ /** {@link #sAppOpsToNote} not initialized yet for this op */
+ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
+ /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
+ private static final byte SHOULD_NOT_COLLECT_NOTE_OP = 1;
+ /** Should collect noting of this app-op in {@link #sAppOpsToNote} */
+ private static final byte SHOULD_COLLECT_NOTE_OP = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "SHOULD_" }, value = {
+ SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED,
+ SHOULD_NOT_COLLECT_NOTE_OP,
+ SHOULD_COLLECT_NOTE_OP
+ })
+ private @interface ShouldCollectNoteOp {}
+
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -1988,6 +2021,27 @@ public class AppOpsManager {
*/
private static HashMap<String, Integer> sPermToOp = new HashMap<>();
+ /**
+ * Set to the uid of the caller if this thread is currently executing a two-way binder
+ * transaction. Not set if this thread is currently not executing a two way binder transaction.
+ *
+ * @see #startNotedAppOpsCollection
+ * @see #markAppOpNoted
+ */
+ private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>();
+
+ /**
+ * If a thread is currently executing a two-way binder transaction, this stores the op-codes of
+ * the app-ops that were noted during this transaction.
+ *
+ * @see #markAppOpNoted
+ */
+ private static final ThreadLocal<long[]> sAppOpsNotedInThisBinderTransaction =
+ new ThreadLocal<>();
+
+ /** Whether noting for an appop should be collected */
+ private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
+
static {
if (sOpToSwitch.length != _NUM_OP) {
throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
@@ -2031,6 +2085,12 @@ public class AppOpsManager {
sPermToOp.put(sOpPerms[op], op);
}
}
+
+ if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
+ // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
+ // two longs
+ throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
+ }
}
/** @hide */
@@ -3220,7 +3280,7 @@ public class AppOpsManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -3251,6 +3311,7 @@ public class AppOpsManager {
return result;
}
+ @NonNull
@Override
public String toString() {
return getClass().getSimpleName() + "[from:"
@@ -3486,7 +3547,7 @@ public class AppOpsManager {
};
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -3718,7 +3779,7 @@ public class AppOpsManager {
};
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -4072,7 +4133,7 @@ public class AppOpsManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -4350,8 +4411,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
@@ -4374,8 +4435,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
@@ -4396,8 +4457,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
@@ -4429,8 +4490,8 @@ public class AppOpsManager {
* The mode of the ops returned are set for the package but may not reflect their effective
* state due to UID policy or because it's controlled by a different master op.
*
- * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
- * if the effective mode is needed.
+ * Use {@link #unsafeCheckOp(String, int, String)}} or
+ * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
*
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
@@ -4820,7 +4881,7 @@ public class AppOpsManager {
*
* @see #isOperationActive
* @see #stopWatchingActive
- * @see #startOp(int, int, String)
+ * @see #startOp(int, int, String, boolean, String)
* @see #finishOp(int, int, String)
*/
// TODO: Uncomment below annotation once b/73559440 is fixed
@@ -4871,7 +4932,7 @@ public class AppOpsManager {
*
* @see #isOperationActive
* @see #startWatchingActive
- * @see #startOp(int, int, String)
+ * @see #startOp(int, int, String, boolean, String)
* @see #finishOp(int, int, String)
*/
public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
@@ -4902,7 +4963,7 @@ public class AppOpsManager {
*
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
* @see #stopWatchingNoted(OnOpNotedListener)
- * @see #noteOp(String, int, String)
+ * @see #noteOp(String, int, String, String)
*
* @hide
*/
@@ -4934,7 +4995,7 @@ public class AppOpsManager {
* Unregistering a non-registered callback has no effect.
*
* @see #startWatchingNoted(int[], OnOpNotedListener)
- * @see #noteOp(String, int, String)
+ * @see #noteOp(String, int, String, String)
*
* @hide
*/
@@ -4969,15 +5030,16 @@ public class AppOpsManager {
/**
* Do a quick check for whether an application might be able to perform an operation.
- * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String)}
- * or {@link #startOp(String, int, String)} for your actual security checks, which also
- * ensure that the given uid and package name are consistent. This function can just be
- * used for a quick check to see if an operation has been disabled for the application,
+ * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+ * String)} or {@link #startOp(String, int, String, String)} for your actual security checks,
+ * which also ensure that the given uid and package name are consistent. This function can just
+ * be used for a quick check to see if an operation has been disabled for the application,
* as an early reject of some work. This does not modify the time stamp or other data
* about the operation.
*
* <p>Important things this will not do (which you need to ultimate use
- * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p>
+ * {@link #noteOp(String, int, String, String)} or
+ * {@link #startOp(String, int, String, String)} to cover):</p>
* <ul>
* <li>Verifying the uid and package are consistent, so callers can't spoof
* their identity.</li>
@@ -5048,126 +5110,305 @@ public class AppOpsManager {
}
/**
+ * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+ */
+ @Deprecated
+ public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
+ return noteOp(op, uid, packageName, null);
+ }
+
+ /**
+ * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+ @Deprecated
+ public int noteOp(int op) {
+ return noteOp(op, Process.myUid(), mContext.getOpPackageName(), null);
+ }
+
+ /**
+ * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+ public int noteOp(int op, int uid, @Nullable String packageName) {
+ return noteOp(op, uid, packageName, null);
+ }
+
+ /**
* Make note of an application performing an operation. Note that you must pass
* in both the uid and name of the application to be checked; this function will verify
* that these two match, and if not, return {@link #MODE_IGNORED}. If this call
* succeeds, the last execution time of the operation for this app will be updated to
* the current time.
+ *
* @param op The operation to note. One of the OPSTR_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
+ *
* @throws SecurityException If the app has been configured to crash on this op.
*/
- public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
- return noteOp(strOpToOp(op), uid, packageName);
+ public int noteOp(@NonNull String op, int uid, @Nullable String packageName,
+ @Nullable String message) {
+ return noteOp(strOpToOp(op), uid, packageName, message);
}
/**
- * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * Make note of an application performing an operation. Note that you must pass
+ * in both the uid and name of the application to be checked; this function will verify
+ * that these two match, and if not, return {@link #MODE_IGNORED}. If this call
+ * succeeds, the last execution time of the operation for this app will be updated to
+ * the current time.
+ *
+ * @param op The operation to note. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
+ *
+ * @throws SecurityException If the app has been configured to crash on this op.
+ *
+ * @hide
*/
+ public int noteOp(int op, int uid, @Nullable String packageName, @Nullable String message) {
+ final int mode = noteOpNoThrow(op, uid, packageName, message);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+ }
+ return mode;
+ }
+
+ /**
+ * @deprecated Use {@link #noteOpNoThrow(String, int, String, String)} instead
+ */
+ @Deprecated
public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
- return noteOpNoThrow(strOpToOp(op), uid, packageName);
+ return noteOpNoThrow(op, uid, packageName, null);
}
/**
- * Make note of an application performing an operation on behalf of another
- * application when handling an IPC. Note that you must pass the package name
- * of the application that is being proxied while its UID will be inferred from
- * the IPC state; this function will verify that the calling uid and proxied
- * package name match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for the proxied app and
- * your app will be updated to the current time.
+ * @deprecated Use {@link #noteOpNoThrow(int, int, String, String)} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteOpNoThrow(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+ public int noteOpNoThrow(int op, int uid, String packageName) {
+ return noteOpNoThrow(op, uid, packageName, null);
+ }
+
+ /**
+ * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
* @param op The operation to note. One of the OPSTR_* constants.
- * @param proxiedPackageName The name of the application calling into the proxy application.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
*/
+ public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String message) {
+ return noteOpNoThrow(strOpToOp(op), uid, packageName, message);
+ }
+
+ /**
+ * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The operation to note. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
+ *
+ * @hide
+ */
+ public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
+ @Nullable String message) {
+ try {
+ int mode = mService.noteOperation(op, uid, packageName);
+ if (mode == MODE_ALLOWED) {
+ markAppOpNoted(uid, packageName, op, message);
+ }
+
+ return mode;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
+ */
+ @Deprecated
public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
- return noteProxyOp(strOpToOp(op), proxiedPackageName);
+ return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
}
/**
- * Like {@link #noteProxyOp(String, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
*
- * <p>This API requires the package with the {@code proxiedPackageName} to belongs to
- * {@link Binder#getCallingUid()}.
+ * @hide
*/
- public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
- return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName);
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+ + "#noteProxyOp(java.lang.String, java.lang.String, int, java.lang.String)} instead")
+ public int noteProxyOp(int op, @Nullable String proxiedPackageName) {
+ return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
}
/**
- * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid.
+ * Make note of an application performing an operation on behalf of another application when
+ * handling an IPC. This function will verify that the calling uid and proxied package name
+ * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+ * time of the operation for the proxied app and your app will be updated to the current time.
*
- * <p>This API requires package with the {@code proxiedPackageName} to belong to
- * {@code proxiedUid}.
+ * @param op The operation to note. One of the OP_* constants.
+ * @param proxiedPackageName The name of the application calling into the proxy application.
+ * @param proxiedUid The uid of the proxied application
+ * @param message A message describing the reason the op was noted
*
- * @param op The op to note
- * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
- * noted for the "android" package
- * @param proxiedUid The uid the package belongs to
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+ * op.
+ *
+ * @hide
*/
- public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
- int proxiedUid) {
- return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid);
+ public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
+ @Nullable String message) {
+ int mode = noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, message);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+ + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
+ + " from uid " + proxiedUid + " not allowed to perform " + sOpNames[op]);
+ }
+ return mode;
}
/**
- * Report that an application has started executing a long-running operation. Note that you
- * must pass in both the uid and name of the application to be checked; this function will
- * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time and the operation will be marked as "running". In this case you must
- * later call {@link #finishOp(String, int, String)} to report when the application is no
- * longer performing the operation.
- * @param op The operation to start. One of the OPSTR_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
+ * Make note of an application performing an operation on behalf of another application when
+ * handling an IPC. This function will verify that the calling uid and proxied package name
+ * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+ * time of the operation for the proxied app and your app will be updated to the current time.
+ *
+ * @param op The operation to note. One of the OPSTR_* constants.
+ * @param proxiedPackageName The name of the application calling into the proxy application.
+ * @param proxiedUid The uid of the proxied application
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+ * op.
*/
- public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
- return startOp(strOpToOp(op), uid, packageName);
+ public int noteProxyOp(@NonNull String op, @Nullable String proxiedPackageName, int proxiedUid,
+ @Nullable String message) {
+ return noteProxyOp(strOpToOp(op), proxiedPackageName, proxiedUid, message);
}
/**
- * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
*/
- public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
- return startOpNoThrow(strOpToOp(op), uid, packageName);
+ @Deprecated
+ public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
+ return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid(), null);
}
/**
- * Report that an application is no longer performing an operation that had previously
- * been started with {@link #startOp(String, int, String)}. There is no validation of input
- * or result; the parameters supplied here must be the exact same ones previously passed
- * in when starting the operation.
+ * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
*/
- public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
- finishOp(strOpToOp(op), uid, packageName);
+ @Deprecated
+ public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+ int proxiedUid) {
+ return noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, null);
+ }
+
+ /**
+ * Like {@link #noteProxyOp(String, String, int, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * <p>This API requires package with the {@code proxiedPackageName} to belong to
+ * {@code proxiedUid}.
+ *
+ * @param op The op to note
+ * @param proxiedPackageName The package to note the op for
+ * @param proxiedUid The uid the package belongs to
+ * @param message A message describing the reason the op was noted
+ */
+ public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+ int proxiedUid, @Nullable String message) {
+ return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid, message);
+ }
+
+ /**
+ * Like {@link #noteProxyOp(int, String, int, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The op to note
+ * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
+ * noted for the "android" package
+ * @param proxiedUid The uid the package belongs to
+ * @param message A message describing the reason the op was noted
+ *
+ * @hide
+ */
+ public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid,
+ @Nullable String message) {
+ int myUid = Process.myUid();
+
+ try {
+ int mode = mService.noteProxyOperation(op, myUid, mContext.getOpPackageName(),
+ proxiedUid, proxiedPackageName);
+ if (mode == MODE_ALLOWED
+ // Only collect app-ops when the proxy is trusted
+ && mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, myUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ markAppOpNoted(proxiedUid, proxiedPackageName, op, message);
+ }
+
+ return mode;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* Do a quick check for whether an application might be able to perform an operation.
- * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
- * or {@link #startOp(int, int, String)} for your actual security checks, which also
- * ensure that the given uid and package name are consistent. This function can just be
- * used for a quick check to see if an operation has been disabled for the application,
- * as an early reject of some work. This does not modify the time stamp or other data
- * about the operation.
+ * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+ * String)} or {@link #startOp(int, int, String, boolean, String)} for your actual security
+ * checks, which also ensure that the given uid and package name are consistent. This function
+ * can just be used for a quick check to see if an operation has been disabled for the
+ * application, as an early reject of some work. This does not modify the time stamp or other
+ * data about the operation.
*
* <p>Important things this will not do (which you need to ultimate use
- * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p>
+ * {@link #noteOp(String, int, String, String)} or
+ * {@link #startOp(int, int, String, boolean, String)} to cover):</p>
* <ul>
* <li>Verifying the uid and package are consistent, so callers can't spoof
* their identity.</li>
@@ -5259,171 +5500,102 @@ public class AppOpsManager {
}
}
- /**
- * Make note of an application performing an operation. Note that you must pass
- * in both the uid and name of the application to be checked; this function will verify
- * that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time.
- * @param op The operation to note. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
- * @hide
- */
+ /** @hide */
@UnsupportedAppUsage
- public int noteOp(int op, int uid, String packageName) {
- final int mode = noteOpNoThrow(op, uid, packageName);
- if (mode == MODE_ERRORED) {
- throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+ public static IBinder getToken(IAppOpsService service) {
+ synchronized (AppOpsManager.class) {
+ if (sToken != null) {
+ return sToken;
+ }
+ try {
+ sToken = service.getToken(new Binder());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return sToken;
}
- return mode;
}
+
/**
- * Make note of an application performing an operation on behalf of another
- * application when handling an IPC. Note that you must pass the package name
- * of the application that is being proxied while its UID will be inferred from
- * the IPC state; this function will verify that the calling uid and proxied
- * package name match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for the proxied app and
- * your app will be updated to the current time.
- * @param op The operation to note. One of the OPSTR_* constants.
- * @param proxiedPackageName The name of the application calling into the proxy application.
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- * @throws SecurityException If the proxy or proxied app has been configured to
- * crash on this op.
- *
- * @hide
+ * @deprecated use {@link #startOp(String, int, String, String)} instead
*/
- @UnsupportedAppUsage
- public int noteProxyOp(int op, String proxiedPackageName) {
- int mode = noteProxyOpNoThrow(op, proxiedPackageName);
- if (mode == MODE_ERRORED) {
- throw new SecurityException("Proxy package " + mContext.getOpPackageName()
- + " from uid " + Process.myUid() + " or calling package "
- + proxiedPackageName + " from uid " + Binder.getCallingUid()
- + " not allowed to perform " + sOpNames[op]);
- }
- return mode;
+ @Deprecated
+ public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
+ return startOp(op, uid, packageName, null);
}
/**
- * Like {@link #noteProxyOp(int, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+ *
* @hide
*/
- public int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) {
- try {
- return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(),
- proxiedUid, proxiedPackageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ @Deprecated
+ public int startOp(int op) {
+ return startOp(op, Process.myUid(), mContext.getOpPackageName(), false, null);
}
/**
- * Like {@link #noteProxyOp(int, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
- *
- * <p>This API requires the package with {@code proxiedPackageName} to belongs to
- * {@link Binder#getCallingUid()}.
+ * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
*
* @hide
*/
- public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
- return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid());
+ @Deprecated
+ public int startOp(int op, int uid, String packageName) {
+ return startOp(op, uid, packageName, false, null);
}
/**
- * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+ *
* @hide
*/
- @UnsupportedAppUsage
- public int noteOpNoThrow(int op, int uid, String packageName) {
- try {
- return mService.noteOperation(op, uid, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public int noteOp(int op) {
- return noteOp(op, Process.myUid(), mContext.getOpPackageName());
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public static IBinder getToken(IAppOpsService service) {
- synchronized (AppOpsManager.class) {
- if (sToken != null) {
- return sToken;
- }
- try {
- sToken = service.getToken(new Binder());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return sToken;
- }
- }
-
- /** @hide */
- public int startOp(int op) {
- return startOp(op, Process.myUid(), mContext.getOpPackageName());
+ @Deprecated
+ public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
+ return startOp(op, uid, packageName, startIfModeDefault, null);
}
/**
- * Report that an application has started executing a long-running operation. Note that you
- * must pass in both the uid and name of the application to be checked; this function will
- * verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time and the operation will be marked as "running". In this case you must
- * later call {@link #finishOp(int, int, String)} to report when the application is no
- * longer performing the operation.
+ * Report that an application has started executing a long-running operation.
*
- * @param op The operation to start. One of the OP_* constants.
+ * @param op The operation to start. One of the OPSTR_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param message Description why op was started
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
- * @throws SecurityException If the app has been configured to crash on this op.
- * @hide
+ *
+ * @throws SecurityException If the app has been configured to crash on this op or
+ * the package is not in the passed in UID.
*/
- public int startOp(int op, int uid, String packageName) {
- return startOp(op, uid, packageName, false);
+ public int startOp(@NonNull String op, int uid, @Nullable String packageName,
+ @Nullable String message) {
+ return startOp(strOpToOp(op), uid, packageName, false, message);
}
/**
- * Report that an application has started executing a long-running operation. Similar
- * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT}
- * the operation should succeed since the caller has performed its standard permission
- * checks which passed and would perform the protected operation for this mode.
+ * Report that an application has started executing a long-running operation.
*
* @param op The operation to start. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+ * @param message Description why op was started
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
- * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
*
* @throws SecurityException If the app has been configured to crash on this op or
* the package is not in the passed in UID.
*
* @hide
*/
- public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
- final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault);
+ public int startOp(int op, int uid, @Nullable String packageName, boolean startIfModeDefault,
+ @Nullable String message) {
+ final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault, message);
if (mode == MODE_ERRORED) {
throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
@@ -5431,45 +5603,111 @@ public class AppOpsManager {
}
/**
- * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
- * returns {@link #MODE_ERRORED}.
+ * @deprecated use {@link #startOpNoThrow(String, int, String, String)} instead
+ */
+ @Deprecated
+ public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+ return startOpNoThrow(op, uid, packageName, null);
+ }
+
+ /**
+ * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+ *
* @hide
*/
+ @Deprecated
public int startOpNoThrow(int op, int uid, String packageName) {
- return startOpNoThrow(op, uid, packageName, false);
+ return startOpNoThrow(op, uid, packageName, false, null);
}
/**
- * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a
+ * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+ return startOpNoThrow(op, uid, packageName, startIfModeDefault, null);
+ }
+
+ /**
+ * Like {@link #startOp(String, int, String, String)} but instead of throwing a
* {@link SecurityException} it returns {@link #MODE_ERRORED}.
*
* @param op The operation to start. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
+ * @param message Description why op was started
+ *
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
+ */
+ public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+ @Nullable String message) {
+ return startOpNoThrow(strOpToOp(op), uid, packageName, false, message);
+ }
+
+ /**
+ * Like {@link #startOp(int, int, String, boolean, String)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The operation to start. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
* @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+ * @param message Description why op was started
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
*
* @hide
*/
- public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+ public int startOpNoThrow(int op, int uid, @NonNull String packageName,
+ boolean startIfModeDefault, @Nullable String message) {
try {
- return mService.startOperation(getToken(mService), op, uid, packageName,
+ int mode = mService.startOperation(getToken(mService), op, uid, packageName,
startIfModeDefault);
+ if (mode == MODE_ALLOWED) {
+ markAppOpNoted(uid, packageName, op, message);
+ }
+
+ return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
+ * @deprecated Use {@link #finishOp(String, int, String)} instead
+ *
+ * @hide
+ */
+ @Deprecated
+ public void finishOp(int op) {
+ finishOp(op, Process.myUid(), mContext.getOpPackageName());
+ }
+
+ /**
* Report that an application is no longer performing an operation that had previously
- * been started with {@link #startOp(int, int, String)}. There is no validation of input
- * or result; the parameters supplied here must be the exact same ones previously passed
+ * been started with {@link #startOp(String, int, String, String)}. There is no validation of
+ * input or result; the parameters supplied here must be the exact same ones previously passed
* in when starting the operation.
+ */
+ public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
+ finishOp(strOpToOp(op), uid, packageName);
+ }
+
+ /**
+ * Report that an application is no longer performing an operation that had previously
+ * been started with {@link #startOp(int, int, String, boolean, String)}. There is no
+ * validation of input or result; the parameters supplied here must be the exact same ones
+ * previously passed in when starting the operation.
+ *
* @hide
*/
- public void finishOp(int op, int uid, String packageName) {
+ public void finishOp(int op, int uid, @NonNull String packageName) {
try {
mService.finishOperation(getToken(mService), op, uid, packageName);
} catch (RemoteException e) {
@@ -5477,25 +5715,399 @@ public class AppOpsManager {
}
}
- /** @hide */
- public void finishOp(int op) {
- finishOp(op, Process.myUid(), mContext.getOpPackageName());
- }
-
/**
* Checks whether the given op for a package is active.
* <p>
* If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS}
* permission you can query only for your UID.
*
- * @see #finishOp(int)
- * @see #startOp(int)
+ * @see #finishOp(String, int, String)
+ * @see #startOp(String, int, String, String)
*/
public boolean isOpActive(@NonNull String op, int uid, @NonNull String packageName) {
return isOperationActive(strOpToOp(op), uid, packageName);
}
/**
+ * Start collection of noted appops on this thread.
+ *
+ * <p>Called at the beginning of a two way binder transaction.
+ *
+ * @see #finishNotedAppOpsCollection()
+ *
+ * @hide
+ */
+ public static void startNotedAppOpsCollection(int callingUid) {
+ sBinderThreadCallingUid.set(callingUid);
+ }
+
+ /**
+ * State of a temporarily paused noted app-ops collection.
+ *
+ * @see #pauseNotedAppOpsCollection()
+ *
+ * @hide
+ */
+ public static class PausedNotedAppOpsCollection {
+ final int mUid;
+ final @Nullable long[] mCollectedNotedAppOps;
+
+ PausedNotedAppOpsCollection(int uid, @Nullable long[] collectedNotedAppOps) {
+ mUid = uid;
+ mCollectedNotedAppOps = collectedNotedAppOps;
+ }
+ }
+
+ /**
+ * Temporarily suspend collection of noted app-ops when binder-thread calls into the other
+ * process. During such a call there might be call-backs coming back on the same thread which
+ * should not be accounted to the current collection.
+ *
+ * @return a state needed to resume the collection
+ *
+ * @hide
+ */
+ public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
+ Integer previousUid = sBinderThreadCallingUid.get();
+ if (previousUid != null) {
+ long[] previousCollectedNotedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+
+ sBinderThreadCallingUid.remove();
+ sAppOpsNotedInThisBinderTransaction.remove();
+
+ return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps);
+ }
+
+ return null;
+ }
+
+ /**
+ * Resume a collection paused via {@link #pauseNotedAppOpsCollection}.
+ *
+ * @param prevCollection The state of the previous collection
+ *
+ * @hide
+ */
+ public static void resumeNotedAppOpsCollection(
+ @Nullable PausedNotedAppOpsCollection prevCollection) {
+ if (prevCollection != null) {
+ sBinderThreadCallingUid.set(prevCollection.mUid);
+
+ if (prevCollection.mCollectedNotedAppOps != null) {
+ sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps);
+ }
+ }
+ }
+
+ /**
+ * Finish collection of noted appops on this thread.
+ *
+ * <p>Called at the end of a two way binder transaction.
+ *
+ * @see #startNotedAppOpsCollection(int)
+ *
+ * @hide
+ */
+ public static void finishNotedAppOpsCollection() {
+ sBinderThreadCallingUid.remove();
+ sAppOpsNotedInThisBinderTransaction.remove();
+ }
+
+ /**
+ * Mark an app-op as noted
+ */
+ private void markAppOpNoted(int uid, @NonNull String packageName, int code,
+ @Nullable String message) {
+ // check it the appops needs to be collected and cache result
+ if (sAppOpsToNote[code] == SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED) {
+ boolean shouldCollectNotes;
+ try {
+ shouldCollectNotes = mService.shouldCollectNotes(code);
+ } catch (RemoteException e) {
+ return;
+ }
+
+ if (shouldCollectNotes) {
+ sAppOpsToNote[code] = SHOULD_COLLECT_NOTE_OP;
+ } else {
+ sAppOpsToNote[code] = SHOULD_NOT_COLLECT_NOTE_OP;
+ }
+ }
+
+ if (sAppOpsToNote[code] != SHOULD_COLLECT_NOTE_OP) {
+ return;
+ }
+
+ Integer binderUid = sBinderThreadCallingUid.get();
+
+ synchronized (sLock) {
+ if (sNotedAppOpsCollector != null && uid == Process.myUid() && packageName.equals(
+ ActivityThread.currentOpPackageName())) {
+ // This app is noting an app-op for itself. Deliver immediately.
+ sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(code));
+ } else if (binderUid != null && binderUid == uid) {
+ // We are inside of a two-way binder call. Delivered to caller via
+ // {@link #prefixParcelWithAppOpsIfNeeded}
+ long[] appOpsNotedInThisBinderTransaction;
+
+ appOpsNotedInThisBinderTransaction = sAppOpsNotedInThisBinderTransaction.get();
+ if (appOpsNotedInThisBinderTransaction == null) {
+ appOpsNotedInThisBinderTransaction = new long[2];
+ sAppOpsNotedInThisBinderTransaction.set(appOpsNotedInThisBinderTransaction);
+ }
+
+ if (code < 64) {
+ appOpsNotedInThisBinderTransaction[0] |= 1L << code;
+ } else {
+ appOpsNotedInThisBinderTransaction[1] |= 1L << (code - 64);
+ }
+ } else {
+ // We cannot deliver the note synchronous. Hence send it to the system server to
+ // notify the noted process.
+ if (message == null) {
+ // Default message is a stack trace
+ message = getFormattedStackTrace();
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ mService.noteAsyncOp(mContext.getOpPackageName(), uid, packageName, code,
+ message);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ /**
+ * Append app-ops noted in the current two-way binder transaction to parcel.
+ *
+ * <p>This is called on the callee side of a two way binder transaction just before the
+ * transaction returns.
+ *
+ * @param p the parcel to append the noted app-ops to
+ *
+ * @hide
+ */
+ public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
+ long[] notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+ if (notedAppOps == null || (notedAppOps[0] == 0 && notedAppOps[1] == 0)) {
+ return;
+ }
+
+ p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+ p.writeLong(notedAppOps[0]);
+ p.writeLong(notedAppOps[1]);
+ }
+
+ /**
+ * Read app-ops noted during a two-way binder transaction from parcel.
+ *
+ * <p>This is called on the calling side of a two way binder transaction just after the
+ * transaction returns.
+ *
+ * <p>Note: Make sure to keep frameworks/native/libs/binder/Status.cpp::readAndLogNotedAppops
+ * in sync.
+ *
+ * @param p The parcel to read from
+ *
+ * @hide
+ */
+ public static void readAndLogNotedAppops(@NonNull Parcel p) {
+ long[] rawNotedAppOps = new long[2];
+ rawNotedAppOps[0] = p.readLong();
+ rawNotedAppOps[1] = p.readLong();
+
+ if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
+ BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+
+ synchronized (sLock) {
+ for (int code = notedAppOps.nextSetBit(0); code != -1;
+ code = notedAppOps.nextSetBit(code + 1)) {
+ if (sNotedAppOpsCollector != null) {
+ sNotedAppOpsCollector.onNoted(new SyncNotedAppOp(code));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a new {@link AppOpsCollector}.
+ *
+ * <p>There can only ever be one collector per process. If there currently is a collector
+ * registered, it will be unregistered.
+ *
+ * <p><b>Only appops related to dangerous permissions are collected.</b>
+ *
+ * @param collector The collector to set or {@code null} to unregister.
+ */
+ public void setNotedAppOpsCollector(@Nullable AppOpsCollector collector) {
+ synchronized (sLock) {
+ if (sNotedAppOpsCollector != null) {
+ try {
+ mService.stopWatchingAsyncNoted(mContext.getPackageName(),
+ sNotedAppOpsCollector.mAsyncCb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ sNotedAppOpsCollector = collector;
+
+ if (sNotedAppOpsCollector != null) {
+ List<AsyncNotedAppOp> missedAsyncOps = null;
+ try {
+ mService.startWatchingAsyncNoted(mContext.getPackageName(),
+ sNotedAppOpsCollector.mAsyncCb);
+ missedAsyncOps = mService.extractAsyncOps(mContext.getPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (missedAsyncOps != null) {
+ int numMissedAsyncOps = missedAsyncOps.size();
+ for (int i = 0; i < numMissedAsyncOps; i++) {
+ final AsyncNotedAppOp asyncNotedAppOp = missedAsyncOps.get(i);
+ if (sNotedAppOpsCollector != null) {
+ sNotedAppOpsCollector.getAsyncNotedExecutor().execute(
+ () -> sNotedAppOpsCollector.onAsyncNoted(
+ asyncNotedAppOp));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} iff the process currently is currently collecting noted appops.
+ *
+ * @see #setNotedAppOpsCollector(AppOpsCollector)
+ *
+ * @hide
+ */
+ public static boolean isCollectingNotedAppOps() {
+ synchronized (sLock) {
+ return sNotedAppOpsCollector != null;
+ }
+ }
+
+ /**
+ * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
+ * appops.
+ *
+ * <p><b>Only appops related to dangerous permissions are collected.</b>
+ */
+ public abstract static class AppOpsCollector {
+ /** Callback registered with the system. This will receive the async notes ops */
+ private final IAppOpsAsyncNotedCallback mAsyncCb = new IAppOpsAsyncNotedCallback.Stub() {
+ @Override
+ public void opNoted(AsyncNotedAppOp op) {
+ Preconditions.checkNotNull(op);
+
+ getAsyncNotedExecutor().execute(() -> onAsyncNoted(op));
+ }
+ };
+
+ /**
+ * @return The executor for the system to use when calling {@link #onAsyncNoted}.
+ */
+ public @NonNull Executor getAsyncNotedExecutor() {
+ return new HandlerExecutor(Handler.getMain());
+ }
+
+ /**
+ * Called when an app-op was noted for this package inside of a two-way binder-call.
+ *
+ * <p>Called on the calling thread just after executing the binder-call. This allows
+ * the app to e.g. collect stack traces to figure out where the access came from.
+ *
+ * @param op The op noted
+ */
+ public abstract void onNoted(@NonNull SyncNotedAppOp op);
+
+ /**
+ * Called when this app noted an app-op for its own package.
+ *
+ * <p>Called on the thread the noted the op. This allows the app to e.g. collect stack
+ * traces to figure out where the access came from.
+ *
+ * @param op The op noted
+ */
+ public abstract void onSelfNoted(@NonNull SyncNotedAppOp op);
+
+ /**
+ * Called when an app-op was noted for this package which cannot be delivered via the other
+ * two mechanisms.
+ *
+ * <p>Called as soon as possible after the app-op was noted, but the delivery delay is not
+ * guaranteed. Due to how async calls work in Android this might even be delivered slightly
+ * before the private data is delivered to the app.
+ *
+ * <p>If the app is not running or no {@link AppOpsCollector} is registered a small amount
+ * of noted app-ops are buffered and then delivered as soon as a collector is registered.
+ *
+ * @param asyncOp The op noted
+ */
+ public abstract void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp);
+ }
+
+ /**
+ * Generate a stack trace used for noted app-ops logging.
+ *
+ * <p>This strips away the first few and last few stack trace elements as they are not
+ * interesting to apps.
+ */
+ private static String getFormattedStackTrace() {
+ StackTraceElement[] trace = new Exception().getStackTrace();
+
+ int firstInteresting = 0;
+ for (int i = 0; i < trace.length; i++) {
+ if (trace[i].getClassName().startsWith(AppOpsManager.class.getName())
+ || trace[i].getClassName().startsWith(Parcel.class.getName())
+ || trace[i].getClassName().contains("$Stub$Proxy")
+ || trace[i].getClassName().startsWith(DatabaseUtils.class.getName())
+ || trace[i].getClassName().startsWith("android.content.ContentProviderProxy")
+ || trace[i].getClassName().startsWith(ContentResolver.class.getName())) {
+ firstInteresting = i;
+ } else {
+ break;
+ }
+ }
+
+ int lastInteresting = trace.length - 1;
+ for (int i = trace.length - 1; i >= 0; i--) {
+ if (trace[i].getClassName().startsWith(HandlerThread.class.getName())
+ || trace[i].getClassName().startsWith(Handler.class.getName())
+ || trace[i].getClassName().startsWith(Looper.class.getName())
+ || trace[i].getClassName().startsWith(Binder.class.getName())
+ || trace[i].getClassName().startsWith(RuntimeInit.class.getName())
+ || trace[i].getClassName().startsWith(ZygoteInit.class.getName())
+ || trace[i].getClassName().startsWith(ActivityThread.class.getName())
+ || trace[i].getClassName().startsWith(Method.class.getName())
+ || trace[i].getClassName().startsWith("com.android.server.SystemServer")) {
+ lastInteresting = i;
+ } else {
+ break;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = firstInteresting; i <= lastInteresting; i++) {
+ sb.append(trace[i]);
+ if (i != lastInteresting) {
+ sb.append('\n');
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
* Checks whether the given op for a UID and package is active.
*
* <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
@@ -5504,7 +6116,7 @@ public class AppOpsManager {
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
* @see #stopWatchingMode(OnOpChangedListener)
* @see #finishOp(int)
- * @see #startOp(int)
+ * @see #startOp(int, int, String, boolean, String)
*
* @hide */
@TestApi
diff --git a/core/tests/coretests/src/android/app/activity/LocalDeniedService.java b/core/java/android/app/AsyncNotedAppOp.aidl
index 3bdac22d9f1f..ebfefd0ba66c 100644
--- a/core/tests/coretests/src/android/app/activity/LocalDeniedService.java
+++ b/core/java/android/app/AsyncNotedAppOp.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.app.activity;
-
-public class LocalDeniedService extends LocalService
-{
-}
+package android.app;
+parcelable AsyncNotedAppOp;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
new file mode 100644
index 000000000000..64f886aa2f1d
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -0,0 +1,245 @@
+/*
+ * 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.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * When an {@link AppOpsManager#noteOp(String, int, String, String) app-op is noted} and the
+ * app the app-op is noted for has a {@link AppOpsManager.AppOpsCollector} registered the note-event
+ * needs to be delivered to the collector. Usually this is done via an {@link SyncNotedAppOp}, but
+ * in some cases this is not possible. In this case an {@link AsyncNotedAppOp} is send to the system
+ * server and then forwarded to the {@link AppOpsManager.AppOpsCollector} in the app.
+ */
+@Immutable
+@DataClass(genEqualsHashCode = true,
+ genAidl = true,
+ genHiddenConstructor = true)
+// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
+// getter
+@DataClass.Suppress({"getOpCode"})
+public final class AsyncNotedAppOp implements Parcelable {
+ /** Op that was noted */
+ private final @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+
+ /** Uid that noted the op */
+ private final @IntRange(from = 0) int mNotingUid;
+
+ /**
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
+ */
+ private final @Nullable String mNotingPackageName;
+
+ /** Message associated with the noteOp. This message is set by the app noting the op */
+ private final @NonNull String mMessage;
+
+ /** Milliseconds since epoch when the op was noted */
+ private final @IntRange(from = 0) long mTime;
+
+ /**
+ * @return Op that was noted.
+ */
+ public @NonNull String getOp() {
+ return AppOpsManager.opToPublicName(mOpCode);
+ }
+
+
+
+ // Code below generated by codegen v1.0.0.
+ //
+ // DO NOT MODIFY!
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
+ //
+ // CHECKSTYLE:OFF Generated code
+
+ /**
+ * Creates a new AsyncNotedAppOp.
+ *
+ * @param opCode
+ * Op that was noted
+ * @param notingUid
+ * Uid that noted the op
+ * @param notingPackageName
+ * Package that noted the op
+ * @param message
+ * Message associated with the noteOp. This message is set by the app noting the op
+ * @param time
+ * Milliseconds since epoch when the op was noted
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public AsyncNotedAppOp(
+ @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
+ @IntRange(from = 0) int notingUid,
+ @Nullable String notingPackageName,
+ @NonNull String message,
+ @IntRange(from = 0) long time) {
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mNotingUid = notingUid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mNotingUid,
+ "from", 0);
+ this.mNotingPackageName = notingPackageName;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mTime = time;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTime,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Uid that noted the op
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getNotingUid() {
+ return mNotingUid;
+ }
+
+ /**
+ * Package that noted the op
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getNotingPackageName() {
+ return mNotingPackageName;
+ }
+
+ /**
+ * Message associated with the noteOp. This message is set by the app noting the op
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getMessage() {
+ return mMessage;
+ }
+
+ /**
+ * Milliseconds since epoch when the op was noted
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) long getTime() {
+ return mTime;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ AsyncNotedAppOp that = (AsyncNotedAppOp) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mOpCode == that.mOpCode
+ && mNotingUid == that.mNotingUid
+ && java.util.Objects.equals(mNotingPackageName, that.mNotingPackageName)
+ && java.util.Objects.equals(mMessage, that.mMessage)
+ && mTime == that.mTime;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mOpCode;
+ _hash = 31 * _hash + mNotingUid;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mNotingPackageName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMessage);
+ _hash = 31 * _hash + Long.hashCode(mTime);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mNotingPackageName != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mOpCode);
+ dest.writeInt(mNotingUid);
+ if (mNotingPackageName != null) dest.writeString(mNotingPackageName);
+ dest.writeString(mMessage);
+ dest.writeLong(mTime);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
+ = new Parcelable.Creator<AsyncNotedAppOp>() {
+ @Override
+ public AsyncNotedAppOp[] newArray(int size) {
+ return new AsyncNotedAppOp[size];
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ public AsyncNotedAppOp createFromParcel(android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int opCode = in.readInt();
+ int notingUid = in.readInt();
+ String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ String message = in.readString();
+ long time = in.readLong();
+ return new AsyncNotedAppOp(
+ opCode,
+ notingUid,
+ notingPackageName,
+ message,
+ time);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1566503083973L,
+ codegenVersion = "1.0.0",
+ sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=90L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+}
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 61867ea737e7..750020eb5bb8 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -178,6 +178,13 @@ oneway interface ITaskStackListener {
*/
void onSingleTaskDisplayDrawn(int displayId);
+ /*
+ * Called when the last task is removed from a display which can only contain one task.
+ *
+ * @param displayId the id of the display from which the window is removed.
+ */
+ void onSingleTaskDisplayEmpty(int displayId);
+
/**
* Called when a task is reparented to a stack on a different display.
*
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ac531186b974..f065ff791dce 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8542,24 +8542,34 @@ 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>This flag has no effect if the app posting the bubble is not in the foreground.
+ * The app is considered foreground if it is visible and on the screen, note that
+ * a foreground service does not qualify.
+ * </p>
*
* <p>Generally this flag should only be set if the user has performed an action to request
* or create a bubble.</p>
+ *
+ * @hide
*/
- private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+ public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
/**
* If set and the app posting the bubble is in the foreground, the bubble will
* be posted <b>without</b> the associated notification in the notification shade.
*
- * <p>If the app posting the bubble is not in the foreground this flag has no effect.</p>
+ * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+ * The app is considered foreground if it is visible and on the screen, note that
+ * a foreground service does not qualify.
+ * </p>
*
* <p>Generally this flag should only be set if the user has performed an action to request
* or create a bubble, or if the user has seen the content in the notification and the
* notification is no longer relevant.</p>
+ *
+ * @hide
*/
- private static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
+ public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
Icon icon, int height, @DimenRes int heightResId) {
@@ -8675,11 +8685,21 @@ public class Notification implements Parcelable
out.writeInt(mDesiredHeightResId);
}
- private void setFlags(int flags) {
+ /**
+ * @hide
+ */
+ public void setFlags(int flags) {
mFlags = flags;
}
/**
+ * @hide
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
* Builder to construct a {@link BubbleMetadata} object.
*/
public static final class Builder {
@@ -8795,6 +8815,8 @@ public class Notification implements Parcelable
* {@link #getIntent()} in a floating window).
*
* <p>This flag has no effect if the app posting the bubble is not in the foreground.
+ * The app is considered foreground if it is visible and on the screen, note that
+ * a foreground service does not qualify.
* </p>
*
* <p>Generally, this flag should only be set if the user has performed an action to
@@ -8813,6 +8835,8 @@ public class Notification implements Parcelable
* the notification shade.
*
* <p>This flag has no effect if the app posting the bubble is not in the foreground.
+ * The app is considered foreground if it is visible and on the screen, note that
+ * a foreground service does not qualify.
* </p>
*
* <p>Generally, this flag should only be set if the user has performed an action to
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 9162626e1b37..9f865b41afc6 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -16,17 +16,19 @@
package android.app;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.FileUtils;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
import android.system.StructTimespec;
-import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -62,6 +64,15 @@ final class SharedPreferencesImpl implements SharedPreferences {
/** If a fsync takes more than {@value #MAX_FSYNC_DURATION_MILLIS} ms, warn */
private static final long MAX_FSYNC_DURATION_MILLIS = 256;
+ /**
+ * There will now be a callback to {@link
+ * OnSharedPreferenceChangeListener#onSharedPreferenceChanged(SharedPreferences, String)} with
+ * a {@code null} key on {@link Editor#clear()}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long CALLBACK_ON_CLEAR_CHANGE = 119147584L;
+
// Lock ordering rules:
// - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock
// - acquire mWritingToDiskLock before EditorImpl.mLock
@@ -94,10 +105,6 @@ final class SharedPreferencesImpl implements SharedPreferences {
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
- @GuardedBy("mLock")
- private final WeakHashMap<OnSharedPreferencesClearListener, Object> mClearListeners =
- new WeakHashMap<>();
-
/** Current memory state (always increasing) */
@GuardedBy("this")
private long mCurrentMemoryStateGeneration;
@@ -258,28 +265,6 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
- @Override
- public void registerOnSharedPreferencesClearListener(
- @NonNull OnSharedPreferencesClearListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null.");
- }
- synchronized (mLock) {
- mClearListeners.put(listener, CONTENT);
- }
- }
-
- @Override
- public void unregisterOnSharedPreferencesClearListener(
- @NonNull OnSharedPreferencesClearListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null.");
- }
- synchronized (mLock) {
- mClearListeners.remove(listener);
- }
- }
-
@GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {
@@ -388,10 +373,9 @@ final class SharedPreferencesImpl implements SharedPreferences {
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
final long memoryStateGeneration;
+ final boolean keysCleared;
@Nullable final List<String> keysModified;
- @Nullable final Set<String> keysCleared;
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
- @Nullable final Set<OnSharedPreferencesClearListener> clearListeners;
final Map<String, Object> mapToWriteToDisk;
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@@ -399,16 +383,14 @@ final class SharedPreferencesImpl implements SharedPreferences {
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
- private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
+ private MemoryCommitResult(long memoryStateGeneration, boolean keysCleared,
+ @Nullable List<String> keysModified,
@Nullable Set<OnSharedPreferenceChangeListener> listeners,
- @Nullable Set<String> keysCleared,
- @Nullable Set<OnSharedPreferencesClearListener> clearListeners,
Map<String, Object> mapToWriteToDisk) {
this.memoryStateGeneration = memoryStateGeneration;
+ this.keysCleared = keysCleared;
this.keysModified = keysModified;
this.listeners = listeners;
- this.keysCleared = keysCleared;
- this.clearListeners = clearListeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
@@ -526,16 +508,14 @@ final class SharedPreferencesImpl implements SharedPreferences {
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);
- notifyClearListeners(mcr);
}
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
+ boolean keysCleared = false;
List<String> keysModified = null;
- Set<String> keysCleared = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
- Set<OnSharedPreferencesClearListener> clearListeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
@@ -557,23 +537,16 @@ final class SharedPreferencesImpl implements SharedPreferences {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
- boolean hasClearListeners = !mClearListeners.isEmpty();
- if (hasClearListeners) {
- keysCleared = new ArraySet<>();
- clearListeners = new HashSet<>(mClearListeners.keySet());
- }
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
- if (hasClearListeners) {
- keysCleared.addAll(mapToWriteToDisk.keySet());
- }
changesMade = true;
mapToWriteToDisk.clear();
}
+ keysCleared = true;
mClear = false;
}
@@ -613,8 +586,8 @@ final class SharedPreferencesImpl implements SharedPreferences {
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
- return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
- keysCleared, clearListeners, mapToWriteToDisk);
+ return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
+ listeners, mapToWriteToDisk);
}
@Override
@@ -641,16 +614,21 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
notifyListeners(mcr);
- notifyClearListeners(mcr);
return mcr.writeToDiskResult;
}
private void notifyListeners(final MemoryCommitResult mcr) {
- if (mcr.listeners == null || mcr.keysModified == null ||
- mcr.keysModified.size() == 0) {
+ if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
+ if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
+ for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
+ if (listener != null) {
+ listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
+ }
+ }
+ }
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
@@ -664,24 +642,6 @@ final class SharedPreferencesImpl implements SharedPreferences {
ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}
-
- private void notifyClearListeners(final MemoryCommitResult mcr) {
- if (mcr.clearListeners == null || mcr.keysCleared == null
- || mcr.keysCleared.isEmpty()) {
- return;
- }
- if (Looper.myLooper() == Looper.getMainLooper()) {
- for (OnSharedPreferencesClearListener listener : mcr.clearListeners) {
- if (listener != null) {
- listener.onSharedPreferencesClear(SharedPreferencesImpl.this,
- mcr.keysCleared);
- }
- }
- } else {
- // Run this function on the main thread.
- ActivityThread.sMainThreadHandler.post(() -> notifyClearListeners(mcr));
- }
- }
}
/**
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 205e7a13092b..28413be29a1d 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -620,6 +620,7 @@ public class StatusBarManager {
mNotificationIcons = true;
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
new file mode 100644
index 000000000000..f7b83d409a02
--- /dev/null
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -0,0 +1,68 @@
+/*
+ * 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.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.Immutable;
+
+/**
+ * Description of an app-op that was noted for the current process.
+ *
+ * <p>This is either delivered after a
+ * {@link AppOpsManager.AppOpsCollector#onNoted(SyncNotedAppOp) two way binder call} or
+ * when the app
+ * {@link AppOpsManager.AppOpsCollector#onSelfNoted(SyncNotedAppOp) notes an app-op for
+ * itself}.
+ */
+@Immutable
+public final class SyncNotedAppOp {
+ private final int mOpCode;
+
+ /**
+ * @return The op that was noted.
+ */
+ public @NonNull String getOp() {
+ return AppOpsManager.opToPublicName(mOpCode);
+ }
+
+ /**
+ * Create a new sync op description
+ *
+ * @param opCode The op that was noted
+ *
+ * @hide
+ */
+ public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode) {
+ mOpCode = opCode;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof SyncNotedAppOp)) {
+ return false;
+ }
+
+ return mOpCode == ((SyncNotedAppOp) other).mOpCode;
+ }
+
+ @Override
+ public int hashCode() {
+ return mOpCode;
+ }
+}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index e3a0e11c3c68..46045faecbd4 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -180,6 +180,10 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
+ public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+ }
+
+ @Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
}
diff --git a/core/java/android/app/Vr2dDisplayProperties.java b/core/java/android/app/Vr2dDisplayProperties.java
index fc200bf05253..d2a49fb82e70 100644
--- a/core/java/android/app/Vr2dDisplayProperties.java
+++ b/core/java/android/app/Vr2dDisplayProperties.java
@@ -18,6 +18,7 @@ package android.app;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -74,6 +75,7 @@ public final class Vr2dDisplayProperties implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "Vr2dDisplayProperties{"
@@ -86,7 +88,7 @@ public final class Vr2dDisplayProperties implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java
index 7854394bb02e..498b68642d9d 100644
--- a/core/java/android/app/backup/RestoreDescription.java
+++ b/core/java/android/app/backup/RestoreDescription.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -52,6 +53,7 @@ public class RestoreDescription implements Parcelable {
/** This package's restore data is a tarball-type full data stream */
public static final int TYPE_FULL_STREAM = 2;
+ @NonNull
@Override
public String toString() {
return "RestoreDescription{" + mPackageName + " : "
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
index 298b0031d726..d14238bb2672 100644
--- a/core/java/android/app/prediction/AppPredictionContext.java
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -90,7 +90,7 @@ public final class AppPredictionContext implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o == this) return true;
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
index 281a16f7715a..e5e06f859ac6 100644
--- a/core/java/android/app/prediction/AppPredictionSessionId.java
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -16,6 +16,7 @@
package android.app.prediction;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -46,7 +47,7 @@ public final class AppPredictionSessionId implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
AppPredictionSessionId other = (AppPredictionSessionId) o;
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
index 147c5000e333..6f2149007b8d 100644
--- a/core/java/android/app/prediction/AppTarget.java
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -151,7 +151,7 @@ public final class AppTarget implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
AppTarget other = (AppTarget) o;
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 54b95639f68f..26ff0c17da2e 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -98,7 +98,7 @@ public final class AppTargetEvent implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
AppTargetEvent other = (AppTargetEvent) o;
diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java
index 3603f5f3ab10..052fdc11ef21 100644
--- a/core/java/android/app/prediction/AppTargetId.java
+++ b/core/java/android/app/prediction/AppTargetId.java
@@ -16,6 +16,7 @@
package android.app.prediction;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -59,7 +60,7 @@ public final class AppTargetId implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
AppTargetId other = (AppTargetId) o;
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index b92d538ce281..b5aed49f211e 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -81,7 +81,7 @@ public final class CacheQuotaHint implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o instanceof CacheQuotaHint) {
final CacheQuotaHint other = (CacheQuotaHint) o;
return Objects.equals(mUuid, other.mUuid)
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index b564c3121d2e..84c68552c40a 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -554,6 +554,7 @@ public final class UsageEvents implements Parcelable {
* event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null;
* @hide
*/
+ @Nullable
@SystemApi
public String getNotificationChannelId() {
return mNotificationChannelId;
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 6fe6e991fb1e..e24512ac525d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.app.AppOpsManager.strOpToOp;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -75,6 +77,16 @@ public final class PermissionChecker {
}
/**
+ * @deprecated Use {@link #checkPermission(Context, String, int, int, String, String)} instead
+ */
+ @Deprecated
+ @PermissionResult
+ public static int checkPermission(@NonNull Context context, @NonNull String permission,
+ int pid, int uid, @Nullable String packageName) {
+ return checkPermission(context, permission, pid, uid, packageName, null);
+ }
+
+ /**
* Checks whether a given package in a UID and PID has a given permission
* and whether the app op that corresponds to this permission is allowed.
*
@@ -84,12 +96,14 @@ public final class PermissionChecker {
* @param uid The uid for which to check.
* @param packageName The package name for which to check. If null the
* the first package for the calling UID will be used.
+ * @param message A message describing the reason the permission was checked
+ *
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
*/
@PermissionResult
public static int checkPermission(@NonNull Context context, @NonNull String permission,
- int pid, int uid, @Nullable String packageName) {
+ int pid, int uid, @Nullable String packageName, @Nullable String message) {
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
@@ -108,7 +122,8 @@ public final class PermissionChecker {
packageName = packageNames[0];
}
- if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid) != AppOpsManager.MODE_ALLOWED) {
+ if (appOpsManager.noteProxyOpNoThrow(strOpToOp(op), packageName, uid, message)
+ != AppOpsManager.MODE_ALLOWED) {
return PERMISSION_DENIED_APP_OP;
}
@@ -131,7 +146,17 @@ public final class PermissionChecker {
public static int checkSelfPermission(@NonNull Context context,
@NonNull String permission) {
return checkPermission(context, permission, Process.myPid(),
- Process.myUid(), context.getPackageName());
+ Process.myUid(), context.getPackageName(), null /* self access */);
+ }
+
+ /**
+ * @deprecated Use {@link #checkCallingPermission(Context, String, String, String)} instead
+ */
+ @Deprecated
+ @PermissionResult
+ public static int checkCallingPermission(@NonNull Context context,
+ @NonNull String permission, @Nullable String packageName) {
+ return checkCallingPermission(context, permission, packageName, null);
}
/**
@@ -142,17 +167,29 @@ public final class PermissionChecker {
* @param permission The permission to check.
* @param packageName The package name making the IPC. If null the
* the first package for the calling UID will be used.
+ * @param message A message describing the reason the permission was checked
+ *
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
*/
@PermissionResult
public static int checkCallingPermission(@NonNull Context context,
- @NonNull String permission, @Nullable String packageName) {
+ @NonNull String permission, @Nullable String packageName, @Nullable String message) {
if (Binder.getCallingPid() == Process.myPid()) {
return PERMISSION_DENIED;
}
return checkPermission(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), packageName);
+ Binder.getCallingUid(), packageName, message);
+ }
+
+ /**
+ * @deprecated Use {@link #checkCallingOrSelfPermission(Context, String, String)} instead
+ */
+ @Deprecated
+ @PermissionResult
+ public static int checkCallingOrSelfPermission(@NonNull Context context,
+ @NonNull String permission) {
+ return checkCallingOrSelfPermission(context, permission, null);
}
/**
@@ -161,15 +198,17 @@ public final class PermissionChecker {
*
* @param context Context for accessing resources.
* @param permission The permission to check.
+ * @param message A message describing the reason the permission was checked
+ *
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
*/
@PermissionResult
public static int checkCallingOrSelfPermission(@NonNull Context context,
- @NonNull String permission) {
+ @NonNull String permission, @Nullable String message) {
String packageName = (Binder.getCallingPid() == Process.myPid())
? context.getPackageName() : null;
return checkPermission(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), packageName);
+ Binder.getCallingUid(), packageName, message);
}
}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 9d87e2550c95..c193868fc3fd 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -16,7 +16,6 @@
package android.content;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import java.util.Map;
@@ -58,33 +57,18 @@ public interface SharedPreferences {
*
* <p>This callback will be run on your main thread.
*
- * <p><em>Note: This callback will not be triggered when preferences are cleared via
- * {@link Editor#clear()}. However, from {@link android.os.Build.VERSION_CODES#R Android R}
- * onwards, you can use {@link OnSharedPreferencesClearListener} to register for
- * {@link Editor#clear()} callbacks.</em>
- *
- * @param sharedPreferences The {@link SharedPreferences} that received
- * the change.
- * @param key The key of the preference that was changed, added, or
- * removed.
- */
- void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
- }
-
- /**
- * Interface definition for a callback to be invoked when shared preferences are cleared.
- */
- public interface OnSharedPreferencesClearListener {
- /**
- * Called when shared preferences are cleared via {@link Editor#clear()}.
- *
- * <p>This callback will be run on your main thread.
+ * <p><em>Note: This callback will not be triggered when preferences are cleared
+ * via {@link Editor#clear()}, unless targeting {@link android.os.Build.VERSION_CODES#R}
+ * on devices running OS versions {@link android.os.Build.VERSION_CODES#R Android R}
+ * or later.</em>
*
* @param sharedPreferences The {@link SharedPreferences} that received the change.
- * @param keys The set of keys that were cleared.
+ * @param key The key of the preference that was changed, added, or removed. Apps targeting
+ * {@link android.os.Build.VERSION_CODES#R} on devices running OS versions
+ * {@link android.os.Build.VERSION_CODES#R Android R} or later, will receive
+ * a {@code null} value when preferences are cleared.
*/
- void onSharedPreferencesClear(@NonNull SharedPreferences sharedPreferences,
- @NonNull Set<String> keys);
+ void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
}
/**
@@ -405,35 +389,4 @@ public interface SharedPreferences {
* @see #registerOnSharedPreferenceChangeListener
*/
void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
-
- /**
- * Registers a callback to be invoked when preferences are cleared via {@link Editor#clear()}.
- *
- * <p class="caution"><strong>Caution:</strong> The preference manager does
- * not currently store a strong reference to the listener. You must store a
- * strong reference to the listener, or it will be susceptible to garbage
- * collection. We recommend you keep a reference to the listener in the
- * instance data of an object that will exist as long as you need the
- * listener.</p>
- *
- * @param listener The callback that will run.
- * @see #unregisterOnSharedPreferencesClearListener
- */
- default void registerOnSharedPreferencesClearListener(
- @NonNull OnSharedPreferencesClearListener listener) {
- throw new UnsupportedOperationException(
- "registerOnSharedPreferencesClearListener not implemented");
- }
-
- /**
- * Unregisters a previous callback for {@link Editor#clear()}.
- *
- * @param listener The callback that should be unregistered.
- * @see #registerOnSharedPreferencesClearListener
- */
- default void unregisterOnSharedPreferencesClearListener(
- @NonNull OnSharedPreferencesClearListener listener) {
- throw new UnsupportedOperationException(
- "unregisterOnSharedPreferencesClearListener not implemented");
- }
}
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index f39fc6674a11..1a78f793c008 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -413,7 +413,7 @@ public final class OverlayInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -448,6 +448,7 @@ public final class OverlayInfo implements Parcelable {
return true;
}
+ @NonNull
@Override
public String toString() {
return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
diff --git a/core/java/android/content/pm/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java
index db8f8c2387d6..73b75df80e5b 100644
--- a/core/java/android/content/pm/SuspendDialogInfo.java
+++ b/core/java/android/content/pm/SuspendDialogInfo.java
@@ -185,7 +185,7 @@ public final class SuspendDialogInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -200,6 +200,7 @@ public final class SuspendDialogInfo implements Parcelable {
&& Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage);
}
+ @NonNull
@Override
public String toString() {
final StringBuilder builder = new StringBuilder("SuspendDialogInfo: {");
diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
index b25ef8d6a88f..350bc30d8d24 100644
--- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java
+++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
@@ -17,6 +17,7 @@
package android.hardware.display;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -136,7 +137,7 @@ public final class AmbientBrightnessDayStats implements Parcelable {
};
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -161,6 +162,7 @@ public final class AmbientBrightnessDayStats implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
StringBuilder bucketBoundariesString = new StringBuilder();
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 5b63dcfa7ad6..4c2e297d2133 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -145,6 +145,7 @@ public final class BrightnessConfiguration implements Parcelable {
return 0;
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder("BrightnessConfiguration{[");
@@ -184,7 +185,7 @@ public final class BrightnessConfiguration implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o == this) {
return true;
}
diff --git a/core/java/android/hardware/display/BrightnessCorrection.java b/core/java/android/hardware/display/BrightnessCorrection.java
index b029accab576..22df778a1368 100644
--- a/core/java/android/hardware/display/BrightnessCorrection.java
+++ b/core/java/android/hardware/display/BrightnessCorrection.java
@@ -18,6 +18,7 @@ package android.hardware.display;
import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -98,13 +99,13 @@ public final class BrightnessCorrection implements Parcelable {
*
* @return A string representation.
*/
+ @NonNull
public String toString() {
return mImplementation.toString();
}
-
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o == this) {
return true;
}
diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
index 136211679c6b..55b07268d201 100644
--- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
@@ -16,6 +16,8 @@
package android.hardware.hdmi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -458,6 +460,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
}
+ @NonNull
@Override
public String toString() {
StringBuffer s = new StringBuffer();
@@ -493,7 +496,7 @@ public class HdmiDeviceInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof HdmiDeviceInfo)) {
return false;
}
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index f53f45891f1a..2623458aff5b 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -15,6 +15,8 @@
*/
package android.hardware.hdmi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -162,6 +164,7 @@ public final class HdmiPortInfo implements Parcelable {
dest.writeInt(mMhlSupported ? 1 : 0);
}
+ @NonNull
@Override
public String toString() {
StringBuffer s = new StringBuffer();
@@ -175,7 +178,7 @@ public final class HdmiPortInfo implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (!(o instanceof HdmiPortInfo)) {
return false;
}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index b5da381a4f99..a11f2e9e8373 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.contexthub.V1_0.ContextHub;
@@ -248,6 +249,7 @@ public class ContextHubInfo implements Parcelable {
return mChrePatchVersion;
}
+ @NonNull
@Override
public String toString() {
String retVal = "";
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index d1190ab28ed5..754327a17dc7 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -192,6 +192,7 @@ public class ContextHubIntentEvent {
return mNanoAppMessage;
}
+ @NonNull
@Override
public String toString() {
String out = "ContextHubIntentEvent[eventType = " + mEventType
diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java
index 1c98427b9c23..6777c53940a6 100644
--- a/core/java/android/hardware/location/ContextHubMessage.java
+++ b/core/java/android/hardware/location/ContextHubMessage.java
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -127,7 +128,7 @@ public class ContextHubMessage implements Parcelable {
out.writeByteArray(mData);
}
- public static final @android.annotation.NonNull Parcelable.Creator<ContextHubMessage> CREATOR
+ public static final @NonNull Parcelable.Creator<ContextHubMessage> CREATOR
= new Parcelable.Creator<ContextHubMessage>() {
public ContextHubMessage createFromParcel(Parcel in) {
return new ContextHubMessage(in);
@@ -138,6 +139,7 @@ public class ContextHubMessage implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
int length = mData.length;
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
index fbbf6870bd5a..78cca9601a2d 100644
--- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.location.Location;
import android.os.Parcel;
@@ -72,7 +73,7 @@ public class GeofenceHardwareMonitorEvent implements Parcelable {
return mLocation;
}
- public static final @android.annotation.NonNull Creator<GeofenceHardwareMonitorEvent> CREATOR =
+ public static final @NonNull Creator<GeofenceHardwareMonitorEvent> CREATOR =
new Creator<GeofenceHardwareMonitorEvent>() {
@Override
public GeofenceHardwareMonitorEvent createFromParcel(Parcel source) {
@@ -108,6 +109,7 @@ public class GeofenceHardwareMonitorEvent implements Parcelable {
parcel.writeParcelable(mLocation, flags);
}
+ @NonNull
@Override
public String toString() {
return String.format(
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index ecd369a42841..c033228441b1 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -79,6 +80,7 @@ public class MemoryRegion implements Parcelable{
return mIsExecutable;
}
+ @NonNull
@Override
public String toString() {
String mask = "";
diff --git a/core/java/android/hardware/location/NanoApp.java b/core/java/android/hardware/location/NanoApp.java
index 3fbb0690698c..6a734f369730 100644
--- a/core/java/android/hardware/location/NanoApp.java
+++ b/core/java/android/hardware/location/NanoApp.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -370,6 +371,7 @@ public class NanoApp implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
String retVal = "Id : " + mAppId;
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 0700dd1be423..1d8b69d0ede9 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -131,6 +132,7 @@ public class NanoAppFilter implements Parcelable {
(versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion()));
}
+ @NonNull
@Override
public String toString() {
return "nanoAppId: 0x" + Long.toHexString(mAppId)
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index a6c754d971e3..ea1175639e23 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -219,6 +219,7 @@ public class NanoAppInstanceInfo implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
String retVal = "handle : " + mHandle;
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 078532a0e644..bb3e81ad0ec1 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -136,7 +137,7 @@ public final class NanoAppMessage implements Parcelable {
out.writeByteArray(mMessageBody);
}
- public static final @android.annotation.NonNull Creator<NanoAppMessage> CREATOR =
+ public static final @NonNull Creator<NanoAppMessage> CREATOR =
new Creator<NanoAppMessage>() {
@Override
public NanoAppMessage createFromParcel(Parcel in) {
@@ -149,6 +150,7 @@ public final class NanoAppMessage implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
int length = mMessageBody.length;
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index f4fd1b6fb75f..ec318b749a5d 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -377,7 +377,7 @@ public final class ProgramList implements AutoCloseable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Filter)) return false;
Filter other = (Filter) obj;
@@ -389,6 +389,7 @@ public final class ProgramList implements AutoCloseable {
return true;
}
+ @NonNull
@Override
public String toString() {
return "Filter [mIdentifierTypes=" + mIdentifierTypes
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index b32185533524..d525753a1874 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -485,6 +485,7 @@ public final class ProgramSelector implements Parcelable {
return new ProgramSelector(programType, primary, secondary, null);
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
@@ -502,7 +503,7 @@ public final class ProgramSelector implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof ProgramSelector)) return false;
ProgramSelector other = (ProgramSelector) obj;
@@ -611,6 +612,7 @@ public final class ProgramSelector implements Parcelable {
return mValue;
}
+ @NonNull
@Override
public String toString() {
return "Identifier(" + mType + ", " + mValue + ")";
@@ -622,7 +624,7 @@ public final class ProgramSelector implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Identifier)) return false;
Identifier other = (Identifier) obj;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index a7ff64412fc3..6ea2ac414704 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -485,6 +485,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "ModuleProperties [mId=" + mId
@@ -507,7 +508,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof ModuleProperties)) return false;
ModuleProperties other = (ModuleProperties) obj;
@@ -660,6 +661,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit="
@@ -679,7 +681,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!(obj instanceof BandDescriptor))
@@ -788,6 +790,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo
@@ -808,7 +811,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
@@ -877,6 +880,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]";
@@ -891,7 +895,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
@@ -997,6 +1001,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "BandConfig [ " + mDescriptor.toString() + "]";
@@ -1011,7 +1016,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!(obj instanceof BandConfig))
@@ -1125,6 +1130,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "FmBandConfig [" + super.toString()
@@ -1145,7 +1151,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
@@ -1317,6 +1323,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "AmBandConfig [" + super.toString()
@@ -1332,7 +1339,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
@@ -1656,6 +1663,7 @@ public class RadioManager {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "ProgramInfo"
@@ -1676,7 +1684,7 @@ public class RadioManager {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof ProgramInfo)) return false;
ProgramInfo other = (ProgramInfo) obj;
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index 76304bda05a1..a882c2fe877c 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -16,6 +16,7 @@
package android.hardware.radio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -279,7 +280,7 @@ public final class RadioMetadata implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof RadioMetadata)) return false;
Bundle otherBundle = ((RadioMetadata) obj).mBundle;
@@ -308,6 +309,7 @@ public final class RadioMetadata implements Parcelable {
mBundle = in.readBundle();
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder("RadioMetadata[");
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 5b5bd7661bc5..f96f47dfaffc 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,6 +22,7 @@ import static android.system.OsConstants.ENOSYS;
import static android.system.OsConstants.EPERM;
import static android.system.OsConstants.EPIPE;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
@@ -829,7 +830,7 @@ public class SoundTrigger {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (obj == null)
@@ -869,6 +870,7 @@ public class SoundTrigger {
return true;
}
+ @NonNull
@Override
public String toString() {
return "RecognitionEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index c674480c2a75..506230ecec86 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -327,7 +327,7 @@ public final class UsbPort {
return false;
}
-
+ @NonNull
@Override
public String toString() {
return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 5e9a410a6b84..43c418e2cb26 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -17,6 +17,7 @@
package android.hardware.usb;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.hardware.usb.V1_0.Constants;
import android.os.Parcel;
@@ -322,6 +323,7 @@ public final class UsbPortStatus implements Parcelable {
return mContaminantProtectionStatus;
}
+ @NonNull
@Override
public String toString() {
return "UsbPortStatus{connected=" + isConnected()
@@ -352,7 +354,7 @@ public final class UsbPortStatus implements Parcelable {
dest.writeInt(mContaminantDetectionStatus);
}
- public static final @android.annotation.NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
+ public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
new Parcelable.Creator<UsbPortStatus>() {
@Override
public UsbPortStatus createFromParcel(Parcel in) {
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 10667aecd128..106b7be5c8d3 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -22,5 +22,5 @@ oneway interface INetworkPolicyListener {
void onMeteredIfacesChanged(in String[] meteredIfaces);
void onRestrictBackgroundChanged(boolean restrictBackground);
void onUidPoliciesChanged(int uid, int uidPolicies);
- void onSubscriptionOverride(int subId, int overrideMask, int overrideValue);
+ void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask);
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d68b57..90327663e34b 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -76,7 +76,7 @@ interface INetworkPolicyManager {
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
String getSubscriptionPlansOwner(int subId);
- void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
+ void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask, long timeoutMillis, String callingPackage);
void factoryReset(String subscriber);
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 83813da80c44..45d0c7313fca 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -861,6 +861,7 @@ public final class IpSecManager {
return mResourceId;
}
+ @NonNull
@Override
public String toString() {
return new StringBuilder()
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 0a9a3c8f5084..a101da7b4b9c 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
@@ -152,7 +153,7 @@ public class NetworkKey implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -166,6 +167,7 @@ public class NetworkKey implements Parcelable {
return Objects.hash(type, wifiKey);
}
+ @NonNull
@Override
public String toString() {
switch (type) {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index bf272625e713..628dcd2691cf 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -380,6 +380,7 @@ public class NetworkPolicyManager {
@Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
@Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
@Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
- @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) { }
+ @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
+ long networkTypeMask) { }
}
}
diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java
index a173b0c2b97b..668e96618f34 100644
--- a/core/java/android/net/RssiCurve.java
+++ b/core/java/android/net/RssiCurve.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -170,7 +172,7 @@ public class RssiCurve implements Parcelable {
* not considered equal to each other.
*/
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -187,6 +189,7 @@ public class RssiCurve implements Parcelable {
return Objects.hash(start, bucketWidth, activeNetworkRssiBoost) ^ Arrays.hashCode(rssiBuckets);
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index effc1aa54735..64b3bf1e0aa0 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Bundle;
@@ -182,7 +183,7 @@ public class ScoredNetwork implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -220,6 +221,7 @@ public class ScoredNetwork implements Parcelable {
return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
}
+ @NonNull
@Override
public String toString() {
StringBuilder out = new StringBuilder(
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index d6deba5d41cc..5bc9953e0d05 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -236,6 +236,7 @@ public final class StaticIpConfiguration implements Parcelable {
return lp;
}
+ @NonNull
@Override
public String toString() {
StringBuffer str = new StringBuffer();
@@ -267,7 +268,7 @@ public final class StaticIpConfiguration implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof StaticIpConfiguration)) return false;
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
index fbc281f26ce8..994c794e6997 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -18,6 +18,7 @@ package android.net;
import static android.util.Patterns.GOOD_IRI_CHAR;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -132,6 +133,7 @@ public class WebAddress {
if (mScheme.equals("")) mScheme = "http";
}
+ @NonNull
@Override
public String toString() {
String port = "";
diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java
index e3a93a8d0b8f..bc9d8c54090a 100644
--- a/core/java/android/net/WifiKey.java
+++ b/core/java/android/net/WifiKey.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -91,7 +93,7 @@ public class WifiKey implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -105,6 +107,7 @@ public class WifiKey implements Parcelable {
return Objects.hash(ssid, bssid);
}
+ @NonNull
@Override
public String toString() {
return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]";
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index 4dd2ace59c62..b1de74e817bc 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -17,6 +17,7 @@
package android.net.apf;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.res.Resources;
@@ -91,6 +92,7 @@ public final class ApfCapabilities implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
@@ -98,7 +100,7 @@ public final class ApfCapabilities implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof ApfCapabilities)) return false;
final ApfCapabilities other = (ApfCapabilities) obj;
return apfVersionSupported == other.apfVersionSupported
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index e9c209c9cb3b..8243be9c1355 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -18,6 +18,7 @@ package android.net.metrics;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -185,6 +186,7 @@ public final class ApfProgramEvent implements IpConnectivityLog.Event {
return 0;
}
+ @NonNull
@Override
public String toString() {
String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever";
@@ -193,7 +195,7 @@ public final class ApfProgramEvent implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(ApfProgramEvent.class))) return false;
final ApfProgramEvent other = (ApfProgramEvent) obj;
return lifetime == other.lifetime
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index b9637774e926..eac5579f9eaa 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -17,6 +17,7 @@
package android.net.metrics;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -260,6 +261,7 @@ public final class ApfStats implements IpConnectivityLog.Event {
return 0;
}
+ @NonNull
@Override
public String toString() {
return new StringBuilder("ApfStats(")
@@ -276,7 +278,7 @@ public final class ApfStats implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(ApfStats.class))) return false;
final ApfStats other = (ApfStats) obj;
return durationMs == other.durationMs
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 2fed7363b713..5f9f50708a49 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -17,6 +17,7 @@
package android.net.metrics;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -97,13 +98,14 @@ public final class DhcpClientEvent implements IpConnectivityLog.Event {
return 0;
}
+ @NonNull
@Override
public String toString() {
return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(DhcpClientEvent.class))) return false;
final DhcpClientEvent other = (DhcpClientEvent) obj;
return TextUtils.equals(msg, other.msg)
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index 876000463cb1..32efb5adafcd 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -16,6 +16,7 @@
package android.net.metrics;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -108,6 +109,7 @@ public final class DhcpErrorEvent implements IpConnectivityLog.Event {
return (0xFFFF0000 & errorCode) | (0xFF & option);
}
+ @NonNull
@Override
public String toString() {
return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index ba05c5900acf..f14abb895cb4 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -17,6 +17,8 @@
package android.net.metrics;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -95,6 +97,7 @@ public final class IpManagerEvent implements IpConnectivityLog.Event {
}
};
+ @NonNull
@Override
public String toString() {
return String.format("IpManagerEvent(%s, %dms)",
@@ -102,7 +105,7 @@ public final class IpManagerEvent implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(IpManagerEvent.class))) return false;
final IpManagerEvent other = (IpManagerEvent) obj;
return eventType == other.eventType
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d4ba2943d72b..79e01d7116b6 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -16,6 +16,8 @@
package android.net.metrics;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -85,6 +87,7 @@ public final class IpReachabilityEvent implements IpConnectivityLog.Event {
}
};
+ @NonNull
@Override
public String toString() {
int hi = eventType & 0xff00;
@@ -94,7 +97,7 @@ public final class IpReachabilityEvent implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(IpReachabilityEvent.class))) return false;
final IpReachabilityEvent other = (IpReachabilityEvent) obj;
return eventType == other.eventType;
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 0c57ec644226..fe603cf9305c 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -17,6 +17,8 @@
package android.net.metrics;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -115,6 +117,7 @@ public final class NetworkEvent implements IpConnectivityLog.Event {
}
};
+ @NonNull
@Override
public String toString() {
return String.format("NetworkEvent(%s, %dms)",
@@ -122,7 +125,7 @@ public final class NetworkEvent implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(NetworkEvent.class))) return false;
final NetworkEvent other = (NetworkEvent) obj;
return eventType == other.eventType
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index 3fd87c23b87d..661f648fc74e 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -17,6 +17,7 @@
package android.net.metrics;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -85,6 +86,7 @@ public final class RaEvent implements IpConnectivityLog.Event {
return 0;
}
+ @NonNull
@Override
public String toString() {
return new StringBuilder("RaEvent(lifetimes: ")
@@ -98,7 +100,7 @@ public final class RaEvent implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(RaEvent.class))) return false;
final RaEvent other = (RaEvent) obj;
return routerLifetime == other.routerLifetime
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 1aaa50d139e0..8fab64ae6c4e 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -18,6 +18,7 @@ package android.net.metrics;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -164,6 +165,7 @@ public final class ValidationProbeEvent implements IpConnectivityLog.Event {
return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
}
+ @NonNull
@Override
public String toString() {
return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
@@ -171,7 +173,7 @@ public final class ValidationProbeEvent implements IpConnectivityLog.Event {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || !(obj.getClass().equals(ValidationProbeEvent.class))) return false;
final ValidationProbeEvent other = (ValidationProbeEvent) obj;
return durationMs == other.durationMs
diff --git a/core/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
index f7e494d830ac..4e88149b8095 100644
--- a/core/java/android/net/util/MultinetworkPolicyTracker.java
+++ b/core/java/android/net/util/MultinetworkPolicyTracker.java
@@ -64,7 +64,7 @@ public class MultinetworkPolicyTracker {
private final Context mContext;
private final Handler mHandler;
- private final Runnable mReevaluateRunnable;
+ private final Runnable mAvoidBadWifiCallback;
private final List<Uri> mSettingsUris;
private final ContentResolver mResolver;
private final SettingObserver mSettingObserver;
@@ -81,12 +81,7 @@ public class MultinetworkPolicyTracker {
public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
mContext = ctx;
mHandler = handler;
- mReevaluateRunnable = () -> {
- if (updateAvoidBadWifi() && avoidBadWifiCallback != null) {
- avoidBadWifiCallback.run();
- }
- updateMeteredMultipathPreference();
- };
+ mAvoidBadWifiCallback = avoidBadWifiCallback;
mSettingsUris = Arrays.asList(
Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
@@ -95,15 +90,15 @@ public class MultinetworkPolicyTracker {
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- reevaluate();
+ reevaluateInternal();
}
};
- TelephonyManager.from(ctx).listen(new PhoneStateListener() {
+ TelephonyManager.from(ctx).listen(new PhoneStateListener(handler.getLooper()) {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
mActiveSubId = subId;
- reevaluate();
+ reevaluateInternal();
}
}, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
@@ -119,7 +114,7 @@ public class MultinetworkPolicyTracker {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
mContext.registerReceiverAsUser(
- mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null);
+ mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler);
reevaluate();
}
@@ -164,7 +159,17 @@ public class MultinetworkPolicyTracker {
@VisibleForTesting
public void reevaluate() {
- mHandler.post(mReevaluateRunnable);
+ mHandler.post(this::reevaluateInternal);
+ }
+
+ /**
+ * Reevaluate the settings. Must be called on the handler thread.
+ */
+ private void reevaluateInternal() {
+ if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) {
+ mAvoidBadWifiCallback.run();
+ }
+ updateMeteredMultipathPreference();
}
public boolean updateAvoidBadWifi() {
diff --git a/core/java/android/os/BatterySaverPolicyConfig.java b/core/java/android/os/BatterySaverPolicyConfig.java
index 3801cbd48cdd..3f6ce4fa807c 100644
--- a/core/java/android/os/BatterySaverPolicyConfig.java
+++ b/core/java/android/os/BatterySaverPolicyConfig.java
@@ -161,6 +161,7 @@ public final class BatterySaverPolicyConfig implements Parcelable {
dest.writeInt(mLocationMode);
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 553372174a4d..2c9333bc1a7c 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
@@ -1029,7 +1030,17 @@ public class Binder implements IBinder {
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
+ (transactionName != null ? transactionName : code));
}
- res = onTransact(code, data, reply, flags);
+
+ if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+ AppOpsManager.startNotedAppOpsCollection(callingUid);
+ try {
+ res = onTransact(code, data, reply, flags);
+ } finally {
+ AppOpsManager.finishNotedAppOpsCollection();
+ }
+ } else {
+ res = onTransact(code, data, reply, flags);
+ }
} catch (RemoteException|RuntimeException e) {
if (observer != null) {
observer.callThrewException(callSession, e);
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index c74cef85f3ea..b3b4f784a04a 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.util.Log;
import android.util.SparseIntArray;
@@ -506,9 +507,18 @@ public final class BinderProxy implements IBinder {
}
}
+ final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
+ AppOpsManager.pauseNotedAppOpsCollection();
+
+ if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isCollectingNotedAppOps()) {
+ flags |= FLAG_COLLECT_NOTED_APP_OPS;
+ }
+
try {
return transactNative(code, data, reply, flags);
} finally {
+ AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
+
if (transactListener != null) {
transactListener.onTransactEnded(session);
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 83f88ad41cfc..12bce8a305f2 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -170,6 +170,11 @@ public interface IBinder {
int FLAG_ONEWAY = 0x00000001;
/**
+ * @hide
+ */
+ int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002;
+
+ /**
* Limit that should be placed on IPC sizes to keep them safely under the
* transaction buffer limit.
* @hide
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index a94fd65943a9..09e1c0f6ab1f 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -255,7 +255,7 @@ public class IncidentManager {
* @inheritDoc
*/
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
index a1f2430f4ee3..7e858e1dc390 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -117,6 +118,7 @@ public final class IncidentReportArgs implements Parcelable {
/**
* Print this report as a string.
*/
+ @NonNull
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Incident(");
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e1b55422274a..50487e9e7a99 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -267,7 +268,9 @@ public final class Parcel {
private static final int EX_UNSUPPORTED_OPERATION = -7;
private static final int EX_SERVICE_SPECIFIC = -8;
private static final int EX_PARCELABLE = -9;
- private static final int EX_HAS_REPLY_HEADER = -128; // special; see below
+ /** @hide */
+ public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; // special; see below
+ private static final int EX_HAS_STRICTMODE_REPLY_HEADER = -128; // special; see below
// EX_TRANSACTION_FAILED is used exclusively in native code.
// see libbinder's binder/Status.h
private static final int EX_TRANSACTION_FAILED = -129;
@@ -1866,6 +1869,8 @@ public final class Parcel {
* @see #readException
*/
public final void writeException(@NonNull Exception e) {
+ AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
int code = 0;
if (e instanceof Parcelable
&& (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1944,6 +1949,8 @@ public final class Parcel {
* @see #readException
*/
public final void writeNoException() {
+ AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
// Despite the name of this function ("write no exception"),
// it should instead be thought of as "write the RPC response
// header", but because this function name is written out by
@@ -1951,14 +1958,14 @@ public final class Parcel {
//
// The response header, in the non-exception case (see also
// writeException above, also called by the AIDL compiler), is
- // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
+ // either a 0 (the default case), or EX_HAS_STRICTMODE_REPLY_HEADER if
// StrictMode has gathered up violations that have occurred
// during a Binder call, in which case we write out the number
// of violations and their details, serialized, before the
// actual RPC respons data. The receiving end of this is
// readException(), below.
if (StrictMode.hasGatheredViolations()) {
- writeInt(EX_HAS_REPLY_HEADER);
+ writeInt(EX_HAS_STRICTMODE_REPLY_HEADER);
final int sizePosition = dataPosition();
writeInt(0); // total size of fat header, to be filled in later
StrictMode.writeGatheredViolationsToParcel(this);
@@ -2005,7 +2012,13 @@ public final class Parcel {
@TestApi
public final int readExceptionCode() {
int code = readInt();
- if (code == EX_HAS_REPLY_HEADER) {
+ if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+ AppOpsManager.readAndLogNotedAppops(this);
+ // Read next header or real exception if there is no more header
+ code = readInt();
+ }
+
+ if (code == EX_HAS_STRICTMODE_REPLY_HEADER) {
int headerSize = readInt();
if (headerSize == 0) {
Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7991cd46b65c..f641731fa08f 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -76,6 +76,16 @@ class ServiceManagerProxy implements IServiceManager {
return mServiceManager.listServices(dumpPriority);
}
+ public void registerForNotifications(String name, IServiceCallback cb)
+ throws RemoteException {
+ throw new RemoteException();
+ }
+
+ public void unregisterForNotifications(String name, IServiceCallback cb)
+ throws RemoteException {
+ throw new RemoteException();
+ }
+
/**
* Same as mServiceManager but used by apps.
*
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 03d5d3e195e0..49ce40bb6ee9 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -15,6 +15,7 @@
*/
package android.os;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -44,6 +45,7 @@ public class ServiceSpecificException extends RuntimeException {
this.errorCode = errorCode;
}
+ @NonNull
@Override
public String toString() {
return super.toString() + " (code " + errorCode + ")";
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 702b41beb071..26da0a0aee07 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -53,7 +53,7 @@ public abstract class VibrationEffect implements Parcelable {
public static final int MAX_AMPLITUDE = 255;
/**
- * A click effect.
+ * A click effect. Use this effect as a baseline, as it's the most common type of click effect.
*
* @see #get(int)
*/
@@ -67,7 +67,7 @@ public abstract class VibrationEffect implements Parcelable {
public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
/**
- * A tick effect.
+ * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
* @see #get(int)
*/
public static final int EFFECT_TICK = Effect.TICK;
@@ -89,7 +89,7 @@ public abstract class VibrationEffect implements Parcelable {
public static final int EFFECT_POP = Effect.POP;
/**
- * A heavy click effect.
+ * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
* @see #get(int)
*/
public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 114de2378a1f..9cc9aac490c7 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,5 +1,6 @@
package android.os;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -205,7 +206,7 @@ public class WorkSource implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o instanceof WorkSource) {
WorkSource other = (WorkSource) o;
@@ -989,6 +990,7 @@ public class WorkSource implements Parcelable {
mTags = tags;
}
+ @NonNull
@Override
public String toString() {
StringBuilder result = new StringBuilder("WorkChain{");
@@ -1015,7 +1017,7 @@ public class WorkSource implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o instanceof WorkChain) {
WorkChain other = (WorkChain) o;
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 42816c064ab0..5e359589dfc2 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -19,6 +19,7 @@ package android.permission;
import android.Manifest;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -144,7 +145,7 @@ public final class PermissionManager {
private final int mTargetSdk;
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SplitPermissionInfo that = (SplitPermissionInfo) o;
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 565843e9bba5..0c1b61d583b3 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -17,6 +17,7 @@
package android.printservice;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
@@ -292,7 +293,7 @@ public final class PrintServiceInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -313,6 +314,7 @@ public final class PrintServiceInfo implements Parcelable {
return true;
}
+ @NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java
index a60be5363d62..87f9af39f5ba 100644
--- a/core/java/android/provider/SearchIndexableData.java
+++ b/core/java/android/provider/SearchIndexableData.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
@@ -139,6 +140,7 @@ public abstract class SearchIndexableData {
context = ctx;
}
+ @NonNull
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java
index 1eb17345f09d..0765b6be0032 100644
--- a/core/java/android/provider/SearchIndexableResource.java
+++ b/core/java/android/provider/SearchIndexableResource.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
@@ -66,6 +67,7 @@ public class SearchIndexableResource extends SearchIndexableData {
super(context);
}
+ @NonNull
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dd3942e084cd..7115da2bb531 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7910,8 +7910,10 @@ public final class Settings {
* Whether the notification bubbles are globally enabled
* The value is boolean (1 or 0).
* @hide
+ * @deprecated use {@link Global#NOTIFICATION_BUBBLES} instead.
*/
@TestApi
+ @Deprecated
public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
/**
@@ -8235,6 +8237,14 @@ public final class Settings {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
/**
+ * Whether the notification bubbles are globally enabled
+ * The value is boolean (1 or 0).
+ * @hide
+ */
+ @TestApi
+ public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+
+ /**
* Whether users are allowed to add more users or guest from lockscreen.
* <p>
* Type: int
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 773be80be270..ae2836f4d915 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -4,17 +4,21 @@
"name": "CtsProviderTestCases",
"options": [
{
- "include-annotation": "android.platform.test.annotations.Presubmit"
+ "exclude-filter": "android.provider.cts.SettingsPanelTest"
}
]
},
{
- "name": "SettingsProviderTest",
- "options": [
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- }
- ]
+ "name": "CalendarProviderTests"
+ },
+ {
+ "name": "ContactsProviderTests"
+ },
+ {
+ "name": "MediaProviderTests"
+ },
+ {
+ "name": "SettingsProviderTest"
}
]
}
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index 9a97bb203f5a..0b44470670de 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -81,6 +81,7 @@ public final class FillRequest {
return mProxy.getSmartSuggestionParams();
}
+ @NonNull
@Override
public String toString() {
return "FillRequest[act=" + getActivityComponent().flattenToShortString()
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index 334487dd6ab4..8b3a001f58e6 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -82,6 +82,7 @@ public abstract class PresentationParams {
return mBounds;
}
+ @NonNull
@Override
public String toString() {
return mBounds.toString();
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index fc781c2035f6..b741cff9328e 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -111,6 +111,7 @@ public final class ActivityEvent implements Parcelable {
}
}
+ @NonNull
@Override
public String toString() {
return new StringBuilder("ActivityEvent[").append(mComponentName.toShortString())
diff --git a/core/java/android/service/euicc/EuiccProfileInfo.java b/core/java/android/service/euicc/EuiccProfileInfo.java
index 702837b3083c..6c357ccdd03d 100644
--- a/core/java/android/service/euicc/EuiccProfileInfo.java
+++ b/core/java/android/service/euicc/EuiccProfileInfo.java
@@ -16,6 +16,7 @@
package android.service.euicc;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
@@ -395,7 +396,7 @@ public final class EuiccProfileInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -430,6 +431,7 @@ public final class EuiccProfileInfo implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "EuiccProfileInfo (nickname="
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index aa11445079cd..8ab687f0d001 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -257,6 +257,7 @@ public final class Adjustment implements Parcelable {
dest.writeString(mIssuer);
}
+ @NonNull
@Override
public String toString() {
return "Adjustment{"
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 2b4c24cbb069..8be114ca321e 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,6 +16,8 @@
package android.service.notification;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.RemoteInput;
@@ -266,7 +268,7 @@ public final class NotificationStats implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -293,6 +295,7 @@ public final class NotificationStats implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("NotificationStats{");
diff --git a/core/java/android/service/notification/SnoozeCriterion.java b/core/java/android/service/notification/SnoozeCriterion.java
index 938cc10fec2a..eb624c9d85f4 100644
--- a/core/java/android/service/notification/SnoozeCriterion.java
+++ b/core/java/android/service/notification/SnoozeCriterion.java
@@ -15,6 +15,7 @@
*/
package android.service.notification;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -118,7 +119,7 @@ public final class SnoozeCriterion implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
diff --git a/core/java/android/service/resolver/ResolverTarget.java b/core/java/android/service/resolver/ResolverTarget.java
index 33b328393028..b3657c458ba7 100644
--- a/core/java/android/service/resolver/ResolverTarget.java
+++ b/core/java/android/service/resolver/ResolverTarget.java
@@ -16,13 +16,10 @@
package android.service.resolver;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.ArrayMap;
-
-import java.util.Map;
/**
* A ResolverTarget contains features by which an app or option will be ranked, in
@@ -173,6 +170,7 @@ public final class ResolverTarget implements Parcelable {
}
// serialize the class to a string.
+ @NonNull
@Override
public String toString() {
return "ResolverTarget{"
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index dc9c85808240..619c507c2bd3 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -229,13 +229,14 @@ public abstract class ExplicitHealthCheckService extends Service {
return mHealthCheckTimeoutMillis;
}
+ @NonNull
@Override
public String toString() {
return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
}
@Override
- public boolean equals(Object other) {
+ public boolean equals(@Nullable Object other) {
if (other == this) {
return true;
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index bd386292f95d..c29d251e6535 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -373,6 +373,7 @@ public final class ContentCaptureEvent implements Parcelable {
}
}
+ @NonNull
@Override
public String toString() {
final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 87be30f476b2..2af7ac729174 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2812,7 +2812,7 @@ public class WebView extends AbsoluteLayout
}
@Override
- public void onProvideContentCaptureStructure(ViewStructure structure, int flags) {
+ public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
mProvider.getViewDelegate().onProvideContentCaptureStructure(structure, flags);
}
diff --git a/core/tests/coretests/src/android/app/activity/LocalGrantedService.java b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
index 7ab0fb4d372c..163e4f7127fb 100644
--- a/core/tests/coretests/src/android/app/activity/LocalGrantedService.java
+++ b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package android.app.activity;
+package com.android.internal.app;
-public class LocalGrantedService extends LocalService
-{
-}
+import android.app.AsyncNotedAppOp;
+// Interface to observe async noted appops
+oneway interface IAppOpsAsyncNotedCallback {
+ void opNoted(in AsyncNotedAppOp op);
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 72dbbf3b8b87..fcf09a982463 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,12 +17,13 @@
package com.android.internal.app;
import android.app.AppOpsManager;
-import android.app.AppOpsManager;
+import android.app.AsyncNotedAppOp;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.os.RemoteCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsNotedCallback;
interface IAppOpsService {
@@ -40,6 +41,9 @@ interface IAppOpsService {
IBinder getToken(IBinder clientToken);
int permissionToOpCode(String permission);
int checkAudioOperation(int code, int usage, int uid, String packageName);
+ void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+ String message);
+ boolean shouldCollectNotes(int opCode);
// End of methods also called by native code.
// Any new method exposed to native must be added after the last one, do not reorder
@@ -82,6 +86,10 @@ interface IAppOpsService {
void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
void stopWatchingNoted(IAppOpsNotedCallback callback);
+ void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+ void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+ List<AsyncNotedAppOp> extractAsyncOps(String packageName);
+
int checkOperationRaw(int code, int uid, String packageName);
void reloadNonHistoricalState();
diff --git a/core/java/com/android/internal/util/AnnotationValidations.java b/core/java/com/android/internal/util/AnnotationValidations.java
index c8afdd47f295..2d3b45023c9d 100644
--- a/core/java/com/android/internal/util/AnnotationValidations.java
+++ b/core/java/com/android/internal/util/AnnotationValidations.java
@@ -61,16 +61,52 @@ public class AnnotationValidations {
}
public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
- String paramName1, int param1, String paramName2, int param2) {
+ String paramName1, long param1, String paramName2, long param2) {
validate(annotation, ignored, value, paramName1, param1);
validate(annotation, ignored, value, paramName2, param2);
}
public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
- String paramName, int param) {
+ String paramName, long param) {
switch (paramName) {
- case "from": if (value < param) invalid(annotation, value, paramName, param); break;
- case "to": if (value > param) invalid(annotation, value, paramName, param); break;
+ case "from":
+ if (value < param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
+ case "to":
+ if (value > param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Validate a long value with two parameters.
+ */
+ public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+ String paramName1, long param1, String paramName2, long param2) {
+ validate(annotation, ignored, value, paramName1, param1);
+ validate(annotation, ignored, value, paramName2, param2);
+ }
+
+ /**
+ * Validate a long value with one parameter.
+ */
+ public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+ String paramName, long param) {
+ switch (paramName) {
+ case "from":
+ if (value < param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
+ case "to":
+ if (value > param) {
+ invalid(annotation, value, paramName, param);
+ }
+ break;
}
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index f2ca0a4e5fad..a568c13d7dde 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -688,6 +688,7 @@ message GlobalSettingsProto {
// Configuration options for smart replies and smart actions in notifications. This is
// encoded as a key=value list separated by commas.
optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Notification notification = 82;
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 9955c512686a..24172098491d 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1270,10 +1270,8 @@
<action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE_DENIED" />
</intent-filter>
</receiver>
- <service android:name="android.app.activity.LocalService">
- <intent-filter>
- <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL" />
- </intent-filter>
+ <service android:name="android.app.activity.ServiceTest$RemoteService"
+ android:process=":RemoteService">
<meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
<meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
<meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
@@ -1281,18 +1279,6 @@
<meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
<meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
</service>
- <service android:name="android.app.activity.LocalDeniedService"
- android:permission="com.android.frameworks.coretests.permission.TEST_DENIED">
- <intent-filter>
- <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED" />
- </intent-filter>
- </service>
- <service android:name="android.app.activity.LocalGrantedService"
- android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED">
- <intent-filter>
- <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED" />
- </intent-filter>
- </service>
<service
android:name="android.service.settings.suggestions.MockSuggestionService"
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index bfd543fdbe12..7f813390552c 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -23,9 +23,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
-import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.RemoteException;
public class LocalReceiver extends BroadcastReceiver {
public LocalReceiver() {
@@ -52,7 +52,7 @@ public class LocalReceiver extends BroadcastReceiver {
public void onServiceDisconnected(ComponentName name) {
}
};
- context.bindService(new Intent(context, LocalService.class), sc, 0);
+ context.bindService(new Intent(context, ServiceTest.RemoteService.class), sc, 0);
context.unbindService(sc);
} catch (ReceiverCallNotAllowedException e) {
//resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/LocalService.java b/core/tests/coretests/src/android/app/activity/LocalService.java
deleted file mode 100644
index c31ca4b30a76..000000000000
--- a/core/tests/coretests/src/android/app/activity/LocalService.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.activity;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.util.Log;
-
-public class LocalService extends Service {
- private final IBinder mBinder = new Binder() {
-
- @Override
- protected boolean onTransact(int code, Parcel data, Parcel reply,
- int flags) throws RemoteException {
- if (code == ServiceTest.SET_REPORTER_CODE) {
- data.enforceInterface(ServiceTest.SERVICE_LOCAL);
- mReportObject = data.readStrongBinder();
- return true;
- } else {
- return super.onTransact(code, data, reply, flags);
- }
- }
-
- };
-
- private IBinder mReportObject;
- private int mStartCount = 1;
-
- public LocalService() {
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- //Log.i("LocalService", "onStart: " + intent);
- if (intent.getExtras() != null) {
- mReportObject = intent.getExtras().getIBinder(ServiceTest.REPORT_OBJ_NAME);
- if (mReportObject != null) {
- try {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
- data.writeInt(mStartCount);
- mStartCount++;
- mReportObject.transact(
- ServiceTest.STARTED_CODE, data, null, 0);
- data.recycle();
- } catch (RemoteException e) {
- }
- }
- }
- }
-
- @Override
- public void onDestroy() {
- Log.i("LocalService", "onDestroy: mReportObject=" + mReportObject);
- if (mReportObject != null) {
- try {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
- mReportObject.transact(
- ServiceTest.DESTROYED_CODE, data, null, 0);
- data.recycle();
- } catch (RemoteException e) {
- }
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("LocalService", "onBind: " + intent);
- return mBinder;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("LocalService", "onUnbind: " + intent);
- if (mReportObject != null) {
- try {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
- mReportObject.transact(
- ServiceTest.UNBIND_CODE, data, null, 0);
- data.recycle();
- } catch (RemoteException e) {
- }
- }
- return true;
- }
-
- @Override
- public void onRebind(Intent intent) {
- Log.i("LocalService", "onUnbind: " + intent);
- if (mReportObject != null) {
- try {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
- mReportObject.transact(
- ServiceTest.REBIND_CODE, data, null, 0);
- data.recycle();
- } catch (RemoteException e) {
- }
- }
- }
-}
diff --git a/core/tests/coretests/src/android/app/activity/MetaDataTest.java b/core/tests/coretests/src/android/app/activity/MetaDataTest.java
index cf27878e897e..be6e276d2806 100644
--- a/core/tests/coretests/src/android/app/activity/MetaDataTest.java
+++ b/core/tests/coretests/src/android/app/activity/MetaDataTest.java
@@ -125,7 +125,7 @@ public class MetaDataTest extends AndroidTestCase {
@SmallTest
public void testServiceWithData() throws Exception {
- ComponentName cn = new ComponentName(mContext, LocalService.class);
+ ComponentName cn = new ComponentName(mContext, ServiceTest.RemoteService.class);
ServiceInfo si = mContext.getPackageManager().getServiceInfo(
cn, PackageManager.GET_META_DATA);
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index 9d2aebd1e6cd..c89f37db7fed 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -16,453 +16,250 @@
package android.app.activity;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.Service;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Binder;
-import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteException;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
-
-// These test binders purport to support an interface whose canonical
-// interface name is ServiceTest.SERVICE_LOCAL
-// Temporarily suppress, this test is causing unit test suite run to fail
-// TODO: remove this suppress
-@Suppress
-public class ServiceTest extends ActivityTestsBase {
-
- public static final String SERVICE_LOCAL =
- "com.android.frameworks.coretests.activity.SERVICE_LOCAL";
- public static final String SERVICE_LOCAL_GRANTED =
- "com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED";
- public static final String SERVICE_LOCAL_DENIED =
- "com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED";
-
- public static final String REPORT_OBJ_NAME = "report";
-
- public static final int STARTED_CODE = 1;
- public static final int DESTROYED_CODE = 2;
- public static final int SET_REPORTER_CODE = 3;
- public static final int UNBIND_CODE = 4;
- public static final int REBIND_CODE = 5;
-
- public static final int STATE_START_1 = 0;
- public static final int STATE_START_2 = 1;
- public static final int STATE_UNBIND = 2;
- public static final int STATE_DESTROY = 3;
- public static final int STATE_REBIND = 4;
- public static final int STATE_UNBIND_ONLY = 5;
- public int mStartState;
-
- public IBinder mStartReceiver = new Binder() {
- @Override
- protected boolean onTransact(int code, Parcel data, Parcel reply,
- int flags) throws RemoteException {
- //Log.i("ServiceTest", "Received code " + code + " in state " + mStartState);
- if (code == STARTED_CODE) {
- data.enforceInterface(SERVICE_LOCAL);
- int count = data.readInt();
- if (mStartState == STATE_START_1) {
- if (count == 1) {
- finishGood();
- } else {
- finishBad("onStart() again on an object when it should have been the first time");
- }
- } else if (mStartState == STATE_START_2) {
- if (count == 2) {
- finishGood();
- } else {
- finishBad("onStart() the first time on an object when it should have been the second time");
- }
- } else {
- finishBad("onStart() was called when not expected (state="+mStartState+")");
- }
- return true;
- } else if (code == DESTROYED_CODE) {
- data.enforceInterface(SERVICE_LOCAL);
- if (mStartState == STATE_DESTROY) {
- finishGood();
- } else {
- finishBad("onDestroy() was called when not expected (state="+mStartState+")");
- }
- return true;
- } else if (code == UNBIND_CODE) {
- data.enforceInterface(SERVICE_LOCAL);
- if (mStartState == STATE_UNBIND) {
- mStartState = STATE_DESTROY;
- } else if (mStartState == STATE_UNBIND_ONLY) {
- finishGood();
- } else {
- finishBad("onUnbind() was called when not expected (state="+mStartState+")");
- }
- return true;
- } else if (code == REBIND_CODE) {
- data.enforceInterface(SERVICE_LOCAL);
- if (mStartState == STATE_REBIND) {
- finishGood();
- } else {
- finishBad("onRebind() was called when not expected (state="+mStartState+")");
- }
- return true;
- } else {
- return super.onTransact(code, data, reply, flags);
- }
- }
- };
- public class EmptyConnection implements ServiceConnection {
- public void onServiceConnected(ComponentName name, IBinder service) {
- }
+import junit.framework.TestCase;
- public void onServiceDisconnected(ComponentName name) {
- }
- }
+import org.junit.Test;
- public class TestConnection implements ServiceConnection {
- private final boolean mExpectDisconnect;
- private final boolean mSetReporter;
- private boolean mMonitor;
- private int mCount;
-
- public TestConnection(boolean expectDisconnect, boolean setReporter) {
- mExpectDisconnect = expectDisconnect;
- mSetReporter = setReporter;
- mMonitor = !setReporter;
- }
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
- void setMonitor(boolean v) {
- mMonitor = v;
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (mSetReporter) {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(SERVICE_LOCAL);
- data.writeStrongBinder(mStartReceiver);
- try {
- service.transact(SET_REPORTER_CODE, data, null, 0);
- } catch (RemoteException e) {
- finishBad("DeadObjectException when sending reporting object");
- }
- data.recycle();
- }
-
- if (mMonitor) {
- mCount++;
- if (mStartState == STATE_START_1) {
- if (mCount == 1) {
- finishGood();
- } else {
- finishBad("onServiceConnected() again on an object when it should have been the first time");
- }
- } else if (mStartState == STATE_START_2) {
- if (mCount == 2) {
- finishGood();
- } else {
- finishBad("onServiceConnected() the first time on an object when it should have been the second time");
- }
- } else {
- finishBad("onServiceConnected() called unexpectedly");
- }
- }
- }
-
- public void onServiceDisconnected(ComponentName name) {
- if (mMonitor) {
- if (mStartState == STATE_DESTROY) {
- if (mExpectDisconnect) {
- finishGood();
- } else {
- finishBad("onServiceDisconnected() when it shouldn't have been");
- }
- } else {
- finishBad("onServiceDisconnected() called unexpectedly");
- }
- }
+/**
+ * Test for verifying the behavior of {@link Service}.
+ * <p>
+ * Tests related to internal behavior are usually placed here, e.g. the restart delay may be
+ * different depending on the current amount of restarting services.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ServiceTest
+ */
+@MediumTest
+public class ServiceTest extends TestCase {
+ private static final String ACTION_SERVICE_STARTED = RemoteService.class.getName() + "_STARTED";
+ private static final String EXTRA_START_CODE = "start_code";
+ private static final String EXTRA_PID = "pid";
+
+ private static final long TIMEOUT_SEC = 5;
+ private static final int NOT_STARTED = -1;
+
+ private final Context mContext = getInstrumentation().getContext();
+ private final Intent mServiceIntent = new Intent(mContext, RemoteService.class);
+ private TestConnection mCurrentConnection;
+
+ @Override
+ public void tearDown() {
+ mContext.stopService(mServiceIntent);
+ if (mCurrentConnection != null) {
+ mContext.unbindService(mCurrentConnection);
+ mCurrentConnection = null;
}
}
- void startExpectResult(Intent service) {
- startExpectResult(service, new Bundle());
+ @Test
+ public void testRestart_stickyStartedService_restarted() {
+ testRestartStartedService(Service.START_STICKY, true /* shouldRestart */);
}
- void startExpectResult(Intent service, Bundle bundle) {
- bundle.putIBinder(REPORT_OBJ_NAME, mStartReceiver);
- boolean success = false;
- try {
- //Log.i("foo", "STATE_START_1");
- mStartState = STATE_START_1;
- getContext().startService(new Intent(service).putExtras(bundle));
- waitForResultOrThrow(5 * 1000, "service to start first time");
- //Log.i("foo", "STATE_START_2");
- mStartState = STATE_START_2;
- getContext().startService(new Intent(service).putExtras(bundle));
- waitForResultOrThrow(5 * 1000, "service to start second time");
- success = true;
- } finally {
- if (!success) {
- try {
- getContext().stopService(service);
- } catch (Exception e) {
- // eat
- }
- }
- }
- //Log.i("foo", "STATE_DESTROY");
- mStartState = STATE_DESTROY;
- getContext().stopService(service);
- waitForResultOrThrow(5 * 1000, "service to be destroyed");
+ @Test
+ public void testRestart_redeliveryStartedService_restarted() {
+ testRestartStartedService(Service.START_FLAG_REDELIVERY, true /* shouldRestart */);
}
- void startExpectNoPermission(Intent service) {
- try {
- getContext().startService(service);
- fail("Expected security exception when starting " + service);
- } catch (SecurityException e) {
- // expected
- }
+ @Test
+ public void testRestart_notStickyStartedService_notRestarted() {
+ testRestartStartedService(Service.START_NOT_STICKY, false /* shouldRestart */);
}
- void bindExpectResult(Intent service) {
- TestConnection conn = new TestConnection(true, false);
- TestConnection conn2 = new TestConnection(false, false);
- boolean success = false;
- try {
- // Expect to see the TestConnection connected.
- mStartState = STATE_START_1;
- getContext().bindService(service, conn, 0);
- getContext().startService(service);
- waitForResultOrThrow(5 * 1000, "existing connection to receive service");
-
- // Expect to see the second TestConnection connected.
- getContext().bindService(service, conn2, 0);
- waitForResultOrThrow(5 * 1000, "new connection to receive service");
-
- getContext().unbindService(conn2);
- success = true;
- } finally {
- if (!success) {
- try {
- getContext().stopService(service);
- getContext().unbindService(conn);
- getContext().unbindService(conn2);
- } catch (Exception e) {
- // eat
- }
- }
- }
-
- // Expect to see the TestConnection disconnected.
- mStartState = STATE_DESTROY;
- getContext().stopService(service);
- waitForResultOrThrow(5 * 1000, "existing connection to lose service");
-
- getContext().unbindService(conn);
-
- conn = new TestConnection(true, true);
- success = false;
- try {
- // Expect to see the TestConnection connected.
- conn.setMonitor(true);
- mStartState = STATE_START_1;
- getContext().bindService(service, conn, 0);
- getContext().startService(service);
- waitForResultOrThrow(5 * 1000, "existing connection to receive service");
-
- success = true;
- } finally {
- if (!success) {
- try {
- getContext().stopService(service);
- getContext().unbindService(conn);
- } catch (Exception e) {
- // eat
- }
- }
- }
+ private void testRestartStartedService(int startFlag, boolean shouldRestart) {
+ final int servicePid = startService(startFlag);
+ assertThat(servicePid, not(NOT_STARTED));
- // Expect to see the service unbind and then destroyed.
- conn.setMonitor(false);
- mStartState = STATE_UNBIND;
- getContext().stopService(service);
- waitForResultOrThrow(5 * 1000, "existing connection to lose service");
-
- getContext().unbindService(conn);
-
- conn = new TestConnection(true, true);
- success = false;
- try {
- // Expect to see the TestConnection connected.
- conn.setMonitor(true);
- mStartState = STATE_START_1;
- getContext().bindService(service, conn, 0);
- getContext().startService(service);
- waitForResultOrThrow(5 * 1000, "existing connection to receive service");
-
- success = true;
- } finally {
- if (!success) {
- try {
- getContext().stopService(service);
- getContext().unbindService(conn);
- } catch (Exception e) {
- // eat
- }
- }
- }
-
- // Expect to see the service unbind but not destroyed.
- conn.setMonitor(false);
- mStartState = STATE_UNBIND_ONLY;
- getContext().unbindService(conn);
- waitForResultOrThrow(5 * 1000, "existing connection to unbind service");
-
- // Expect to see the service rebound.
- mStartState = STATE_REBIND;
- getContext().bindService(service, conn, 0);
- waitForResultOrThrow(5 * 1000, "existing connection to rebind service");
+ final int restartedServicePid = waitForServiceStarted(
+ () -> Process.killProcess(servicePid));
+ assertThat(restartedServicePid, shouldRestart ? not(NOT_STARTED) : is(NOT_STARTED));
+ }
- // Expect to see the service unbind and then destroyed.
- mStartState = STATE_UNBIND;
- getContext().stopService(service);
- waitForResultOrThrow(5 * 1000, "existing connection to lose service");
+ @Test
+ public void testRestart_boundService_restarted() {
+ final int servicePid = bindService(Context.BIND_AUTO_CREATE);
+ assertThat(servicePid, not(NOT_STARTED));
- getContext().unbindService(conn);
+ Process.killProcess(servicePid);
+ // The service should be restarted and the connection will receive onServiceConnected again.
+ assertThat(mCurrentConnection.takePid(), not(NOT_STARTED));
}
- void bindAutoExpectResult(Intent service) {
- TestConnection conn = new TestConnection(false, true);
- boolean success = false;
- try {
- conn.setMonitor(true);
- mStartState = STATE_START_1;
- getContext().bindService(
- service, conn, Context.BIND_AUTO_CREATE);
- waitForResultOrThrow(5 * 1000, "connection to start and receive service");
- success = true;
- } finally {
- if (!success) {
- try {
- getContext().unbindService(conn);
- } catch (Exception e) {
- // eat
+ @Test
+ public void testRestart_boundNotStickyStartedService_restarted() {
+ final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+ final Supplier<RunningServiceInfo> serviceInfoGetter = () -> {
+ for (RunningServiceInfo rs : am.getRunningServices(Integer.MAX_VALUE)) {
+ if (mServiceIntent.getComponent().equals(rs.service)) {
+ return rs;
}
}
- }
- mStartState = STATE_UNBIND;
- getContext().unbindService(conn);
- waitForResultOrThrow(5 * 1000, "disconnecting from service");
- }
-
- void bindExpectNoPermission(Intent service) {
- TestConnection conn = new TestConnection(false, false);
- try {
- getContext().bindService(service, conn, Context.BIND_AUTO_CREATE);
- fail("Expected security exception when binding " + service);
- } catch (SecurityException e) {
- // expected
- } finally {
- getContext().unbindService(conn);
- }
- }
-
-
- @MediumTest
- public void testLocalStartClass() throws Exception {
- startExpectResult(new Intent(getContext(), LocalService.class));
- }
-
- @MediumTest
- public void testLocalStartAction() throws Exception {
- startExpectResult(new Intent(SERVICE_LOCAL));
- }
-
- @MediumTest
- public void testLocalBindClass() throws Exception {
- bindExpectResult(new Intent(getContext(), LocalService.class));
- }
-
- @MediumTest
- public void testLocalBindAction() throws Exception {
- bindExpectResult(new Intent(SERVICE_LOCAL));
- }
-
- @MediumTest
- public void testLocalBindAutoClass() throws Exception {
- bindAutoExpectResult(new Intent(getContext(), LocalService.class));
+ return null;
+ };
+
+ final int servicePid = bindService(Context.BIND_AUTO_CREATE);
+ assertThat(servicePid, not(NOT_STARTED));
+ assertThat(startService(Service.START_NOT_STICKY), is(servicePid));
+
+ RunningServiceInfo info = serviceInfoGetter.get();
+ assertThat(info, notNullValue());
+ assertThat(info.started, is(true));
+
+ Process.killProcess(servicePid);
+ // The service will be restarted for connection but the started state should be gone.
+ final int restartedServicePid = mCurrentConnection.takePid();
+ assertThat(restartedServicePid, not(NOT_STARTED));
+
+ info = serviceInfoGetter.get();
+ assertThat(info, notNullValue());
+ assertThat(info.started, is(false));
+ assertThat(info.clientCount, is(1));
}
- @MediumTest
- public void testLocalBindAutoAction() throws Exception {
- bindAutoExpectResult(new Intent(SERVICE_LOCAL));
- }
+ @Test
+ public void testRestart_notStickyStartedNoAutoCreateBoundService_notRestarted() {
+ final int servicePid = startService(Service.START_NOT_STICKY);
+ assertThat(servicePid, not(NOT_STARTED));
+ assertThat(bindService(0 /* flags */), is(servicePid));
- @MediumTest
- public void testLocalStartClassPermissionGranted() throws Exception {
- startExpectResult(new Intent(getContext(), LocalGrantedService.class));
+ Process.killProcess(servicePid);
+ assertThat(mCurrentConnection.takePid(), is(NOT_STARTED));
}
- @MediumTest
- public void testLocalStartActionPermissionGranted() throws Exception {
- startExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
+ /** @return The pid of the started service. */
+ private int startService(int code) {
+ return waitForServiceStarted(
+ () -> mContext.startService(mServiceIntent.putExtra(EXTRA_START_CODE, code)));
}
- @MediumTest
- public void testLocalBindClassPermissionGranted() throws Exception {
- bindExpectResult(new Intent(getContext(), LocalGrantedService.class));
- }
+ /** @return The pid of the started service. */
+ private int waitForServiceStarted(Runnable serviceTrigger) {
+ final CompletableFuture<Integer> pidResult = new CompletableFuture<>();
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
+ mContext.unregisterReceiver(this);
+ }
+ }, new IntentFilter(ACTION_SERVICE_STARTED));
- @MediumTest
- public void testLocalBindActionPermissionGranted() throws Exception {
- bindExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
+ serviceTrigger.run();
+ try {
+ return pidResult.get(TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
+ }
+ return NOT_STARTED;
}
- @MediumTest
- public void testLocalBindAutoClassPermissionGranted() throws Exception {
- bindAutoExpectResult(new Intent(getContext(), LocalGrantedService.class));
+ /** @return The pid of the bound service. */
+ private int bindService(int flags) {
+ mCurrentConnection = new TestConnection();
+ assertThat(mContext.bindService(mServiceIntent, mCurrentConnection, flags), is(true));
+ return mCurrentConnection.takePid();
}
- @MediumTest
- public void testLocalBindAutoActionPermissionGranted() throws Exception {
- bindAutoExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
- }
+ private static class TestConnection implements ServiceConnection {
+ private CompletableFuture<Integer> mServicePid = new CompletableFuture<>();
+
+ /**
+ * @return The pid of the connected service. It is only valid once after
+ * {@link #onServiceConnected} is called.
+ */
+ int takePid() {
+ try {
+ return mServicePid.get(TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
+ } finally {
+ mServicePid = new CompletableFuture<>();
+ }
+ return NOT_STARTED;
+ }
- @MediumTest
- public void testLocalStartClassPermissionDenied() throws Exception {
- startExpectNoPermission(new Intent(getContext(), LocalDeniedService.class));
- }
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final Parcel data = Parcel.obtain();
+ final Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(RemoteService.DESCRIPTOR);
+ try {
+ service.transact(RemoteService.TRANSACTION_GET_PID, data, reply, 0 /* flags */);
+ reply.readException();
+ mServicePid.complete(reply.readInt());
+ } catch (RemoteException e) {
+ mServicePid.complete(NOT_STARTED);
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
- @MediumTest
- public void testLocalStartActionPermissionDenied() throws Exception {
- startExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED));
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
}
- @MediumTest
- public void testLocalBindClassPermissionDenied() throws Exception {
- bindExpectNoPermission(new Intent(getContext(), LocalDeniedService.class));
- }
+ public static class RemoteService extends Service {
+ static final String DESCRIPTOR = RemoteService.class.getName();
+ static final int TRANSACTION_GET_PID = Binder.FIRST_CALL_TRANSACTION;
- @MediumTest
- public void testLocalBindActionPermissionDenied() throws Exception {
- bindExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED));
- }
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ new Handler().post(() -> {
+ final Intent responseIntent = new Intent(ACTION_SERVICE_STARTED);
+ responseIntent.putExtra(EXTRA_PID, Process.myPid());
+ sendBroadcast(responseIntent);
+ });
+ if (intent != null && intent.hasExtra(EXTRA_START_CODE)) {
+ return intent.getIntExtra(EXTRA_START_CODE, Service.START_NOT_STICKY);
+ }
+ return super.onStartCommand(intent, flags, startId);
+ }
- @MediumTest
- public void testLocalUnbindTwice() throws Exception {
- EmptyConnection conn = new EmptyConnection();
- getContext().bindService(
- new Intent(SERVICE_LOCAL_GRANTED), conn, 0);
- getContext().unbindService(conn);
- try {
- getContext().unbindService(conn);
- fail("No exception thrown on second unbind");
- } catch (IllegalArgumentException e) {
- //Log.i("foo", "Unbind exception", e);
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code == TRANSACTION_GET_PID) {
+ data.enforceInterface(DESCRIPTOR);
+ reply.writeNoException();
+ reply.writeInt(Process.myPid());
+ return true;
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+ };
}
}
}
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index b749e715316a..9de450196fa0 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -29,8 +29,10 @@ public class ProcessTest extends TestCase {
assertEquals(Process.BLUETOOTH_UID, Process.getUidForName("bluetooth"));
assertEquals(Process.FIRST_APPLICATION_UID, Process.getUidForName("u0_a0"));
assertEquals(UserHandle.getUid(1, Process.SYSTEM_UID), Process.getUidForName("u1_system"));
- assertEquals(UserHandle.getUid(2, Process.FIRST_ISOLATED_UID),
+ assertEquals(UserHandle.getUid(2, Process.FIRST_APP_ZYGOTE_ISOLATED_UID),
Process.getUidForName("u2_i0"));
+ assertEquals(UserHandle.getUid(2, Process.FIRST_ISOLATED_UID),
+ Process.getUidForName("u2_i9000"));
assertEquals(UserHandle.getUid(3, Process.FIRST_APPLICATION_UID + 100),
Process.getUidForName("u3_a100"));
}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 8a035dbbc0f5..535386920265 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -7402,12 +7402,12 @@ void ResTable::print_value(const Package* pkg, const Res_value& value) const
print_complex(value.data, true);
printf("\n");
} else if (value.dataType >= Res_value::TYPE_FIRST_COLOR_INT
- || value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
+ && value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
printf("(color) #%08x\n", value.data);
} else if (value.dataType == Res_value::TYPE_INT_BOOLEAN) {
printf("(boolean) %s\n", value.data ? "true" : "false");
} else if (value.dataType >= Res_value::TYPE_FIRST_INT
- || value.dataType <= Res_value::TYPE_LAST_INT) {
+ && value.dataType <= Res_value::TYPE_LAST_INT) {
printf("(int) 0x%08x or %d\n", value.data, value.data);
} else {
printf("(unknown type) t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)\n",
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 36fe8dab5dea..2e2f98432d2e 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
/**
@@ -164,6 +165,7 @@ public final class GnssCapabilities {
return hasCapability(MEASUREMENT_CORRECTIONS_REFLECTING_PLANE);
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder("GnssCapabilities: ( ");
diff --git a/location/java/android/location/GnssMeasurementCorrections.java b/location/java/android/location/GnssMeasurementCorrections.java
index 3e32c21da23b..a23213ff260c 100644
--- a/location/java/android/location/GnssMeasurementCorrections.java
+++ b/location/java/android/location/GnssMeasurementCorrections.java
@@ -176,6 +176,7 @@ public final class GnssMeasurementCorrections implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
final String format = " %-29s = %s\n";
diff --git a/location/java/android/location/GnssReflectingPlane.java b/location/java/android/location/GnssReflectingPlane.java
index 9d05287f6b76..1acdd1ecce0b 100644
--- a/location/java/android/location/GnssReflectingPlane.java
+++ b/location/java/android/location/GnssReflectingPlane.java
@@ -107,6 +107,7 @@ public final class GnssReflectingPlane implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
final String format = " %-29s = %s\n";
diff --git a/location/java/android/location/GnssSingleSatCorrection.java b/location/java/android/location/GnssSingleSatCorrection.java
index e9019097e327..aeca562fced2 100644
--- a/location/java/android/location/GnssSingleSatCorrection.java
+++ b/location/java/android/location/GnssSingleSatCorrection.java
@@ -268,6 +268,7 @@ public final class GnssSingleSatCorrection implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
final String format = " %-29s = %s\n";
diff --git a/location/java/android/location/GpsClock.java b/location/java/android/location/GpsClock.java
index 52ba60e36ffd..f1237662a559 100644
--- a/location/java/android/location/GpsClock.java
+++ b/location/java/android/location/GpsClock.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -437,6 +438,7 @@ public class GpsClock implements Parcelable {
return 0;
}
+ @NonNull
@Override
public String toString() {
final String format = " %-15s = %s\n";
diff --git a/location/java/android/location/GpsMeasurement.java b/location/java/android/location/GpsMeasurement.java
index 51718b85103a..27a81899bedc 100644
--- a/location/java/android/location/GpsMeasurement.java
+++ b/location/java/android/location/GpsMeasurement.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1244,6 +1245,7 @@ public class GpsMeasurement implements Parcelable {
return 0;
}
+ @NonNull
@Override
public String toString() {
final String format = " %-29s = %s\n";
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
index 1cd1fb4e3cb2..d69158d335f4 100644
--- a/location/java/android/location/GpsMeasurementsEvent.java
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -140,6 +140,7 @@ public class GpsMeasurementsEvent implements Parcelable {
parcel.writeTypedArray(measurementsArray, flags);
}
+ @NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[ GpsMeasurementsEvent:\n\n");
diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java
index 77f011374dab..6eeea2623a8b 100644
--- a/location/java/android/location/GpsNavigationMessage.java
+++ b/location/java/android/location/GpsNavigationMessage.java
@@ -290,6 +290,7 @@ public class GpsNavigationMessage implements Parcelable {
return 0;
}
+ @NonNull
@Override
public String toString() {
final String format = " %-15s = %s\n";
diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java
index 2aa685cca757..f60e5c71ab16 100644
--- a/location/java/android/location/GpsNavigationMessageEvent.java
+++ b/location/java/android/location/GpsNavigationMessageEvent.java
@@ -109,6 +109,7 @@ public class GpsNavigationMessageEvent implements Parcelable {
parcel.writeParcelable(mNavigationMessage, flags);
}
+ @NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[ GpsNavigationMessageEvent:\n\n");
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index a05d8501bc1f..0902acf176d4 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -734,6 +734,7 @@ public final class LocationRequest implements Parcelable {
}
}
+ @NonNull
@Override
public String toString() {
StringBuilder s = new StringBuilder();
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index ee89509951df..675cf7360b82 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -155,7 +156,7 @@ public final class AudioFocusInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj)
return true;
if (obj == null)
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index e456dad88fa4..2799d46cbb47 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -299,6 +299,7 @@ public final class AudioProductStrategy implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
StringBuilder s = new StringBuilder();
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 6fd3342fc84e..92fb31bd4de8 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -1006,7 +1006,7 @@ public final class MediaSessionManager {
* @return {@code true} if equals, {@code false} otherwise
*/
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof RemoteUserInfo)) {
return false;
}
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 5b316bedd134..b12f7c551288 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -19,12 +19,14 @@ package android.media.tv;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.hardware.tv.input.V1_0.Constants;
import android.media.AudioManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+
import java.lang.annotation.Retention;
/**
@@ -141,6 +143,7 @@ public final class TvInputHardwareInfo implements Parcelable {
return mCableConnectionStatus;
}
+ @NonNull
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java
index f012b46a4a62..7ea93b4f2b23 100644
--- a/media/java/android/media/tv/TvStreamConfig.java
+++ b/media/java/android/media/tv/TvStreamConfig.java
@@ -16,6 +16,8 @@
package android.media.tv;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -87,6 +89,7 @@ public class TvStreamConfig implements Parcelable {
return mGeneration;
}
+ @NonNull
@Override
public String toString() {
return "TvStreamConfig {mStreamId=" + mStreamId + ";" + "mType=" + mType + ";mGeneration="
@@ -163,7 +166,7 @@ public class TvStreamConfig implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null) return false;
if (!(obj instanceof TvStreamConfig)) return false;
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index e09eeb8537c1..ea00d6eff12e 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -394,6 +394,7 @@ public final class TvTrackInfo implements Parcelable {
*
* @param encrypted The encryption status of the track.
*/
+ @NonNull
public Builder setEncrypted(boolean encrypted) {
mEncrypted = encrypted;
return this;
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8d420e2c5598..cbc820b6fde0 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -743,8 +743,8 @@ status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const
}
status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
-
- status_t status = mCodec->getMetrics(reply);
+ mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply);
+ status_t status = mCodec->getMetrics(reply2);
return status;
}
@@ -1848,7 +1848,7 @@ android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz)
}
// get what we have for the metrics from the codec
- MediaAnalyticsItem *item = NULL;
+ MediaAnalyticsItem *item = 0;
status_t err = codec->getMetrics(env, item);
if (err != OK) {
@@ -1860,7 +1860,7 @@ android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz)
// housekeeping
delete item;
- item = NULL;
+ item = 0;
return mybundle;
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index ec40cc377594..afd722ba0091 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -18,6 +18,8 @@ package com.android.systemui.car;
import android.content.Context;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -30,8 +32,10 @@ public class CarNotificationInterruptionStateProvider extends
NotificationInterruptionStateProvider {
@Inject
- public CarNotificationInterruptionStateProvider(Context context) {
- super(context);
+ public CarNotificationInterruptionStateProvider(Context context,
+ NotificationFilter filter,
+ StatusBarStateController stateController) {
+ super(context, filter, stateController);
}
@Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
index 78bb1bcf24a8..013c63b834ff 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
@@ -192,23 +192,28 @@ class CarTrustAgentUnlockDialogHelper extends BroadcastReceiver{
}
}
- // Set button text based on security lock type
+ // Set button text based on screen lock type
private void setButtonText() {
LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
int passwordQuality = lockPatternUtils.getActivePasswordQuality(mUid);
switch (passwordQuality) {
// PIN
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+ mButton.setText(R.string.unlock_dialog_button_text_pin);
+ break;
// Pattern
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
mButton.setText(R.string.unlock_dialog_button_text_pattern);
break;
// Password
- case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
mButton.setText(R.string.unlock_dialog_button_text_password);
break;
default:
- Log.e(TAG, "Encountered unexpected security type when attempting to set "
+ Log.e(TAG, "Encountered unexpected screen lock type when attempting to set "
+ "button text:" + passwordQuality);
}
}
diff --git a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml
index 363d88544a03..7846be161c0f 100644
--- a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="search_menu" msgid="1604061903696928905">"Pesquisar definições"</string>
+ <string name="search_menu" msgid="1604061903696928905">"Definições de pesquisa"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index f1fc9f9e3d4e..13890e029274 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -452,7 +452,7 @@
<string name="cancel" msgid="6859253417269739139">"বাতিল"</string>
<string name="okay" msgid="1997666393121016642">"ঠিক আছে"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="8287824809739581837">"চালু করুন"</string>
- <string name="zen_mode_settings_turn_on_dialog_title" msgid="2297134204747331078">"\'বিরক্ত করবে না\' মোড চালু করুন"</string>
+ <string name="zen_mode_settings_turn_on_dialog_title" msgid="2297134204747331078">"\'বিরক্ত করবেন না\' মোড চালু করুন"</string>
<string name="zen_mode_settings_summary_off" msgid="6119891445378113334">"কখনও নয়"</string>
<string name="zen_interruption_level_priority" msgid="2078370238113347720">"শুধুমাত্র অগ্রাধিকার"</string>
<string name="zen_mode_and_condition" msgid="4927230238450354412">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 7874aeb02996..beb1ac546949 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -141,7 +141,7 @@
<string name="data_usage_uninstalled_apps" msgid="614263770923231598">"Aplicaciones eliminadas"</string>
<string name="data_usage_uninstalled_apps_users" msgid="7986294489899813194">"Aplicaciones y usuarios eliminados"</string>
<string name="data_usage_ota" msgid="5377889154805560860">"Actualizaciones del sistema"</string>
- <string name="tether_settings_title_usb" msgid="6688416425801386511">"Conexión a red por USB"</string>
+ <string name="tether_settings_title_usb" msgid="6688416425801386511">"Conexión USB"</string>
<string name="tether_settings_title_wifi" msgid="3277144155960302049">"Hotspot portátil"</string>
<string name="tether_settings_title_bluetooth" msgid="355855408317564420">"Conexión Bluetooth"</string>
<string name="tether_settings_title_usb_bluetooth" msgid="5355828977109785001">"Compartir conexión"</string>
@@ -286,7 +286,7 @@
<string name="wait_for_debugger_summary" msgid="1766918303462746804">"Esperar que se conecte el depurador para iniciar la aplicación"</string>
<string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
<string name="debug_drawing_category" msgid="6755716469267367852">"Dibujo"</string>
- <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Procesamiento acelerado mediante hardware"</string>
+ <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Representación acelerada mediante hardware"</string>
<string name="media_category" msgid="4388305075496848353">"Multimedia"</string>
<string name="debug_monitoring_category" msgid="7640508148375798343">"Supervisión"</string>
<string name="strict_mode" msgid="1938795874357830695">"Modo estricto"</string>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index 3d9a78e99204..5ad9b0191c77 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -76,7 +76,7 @@
<item msgid="3422726142222090896">"avrcp16"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="7065842274271279580">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="7065842274271279580">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="7539690996561263909">"SBC"</item>
<item msgid="686685526567131661">"AAC"</item>
<item msgid="5254942598247222737">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
@@ -86,7 +86,7 @@
<item msgid="3304843301758635896">"वैकल्पिक कोडेक अक्षम करें"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="5062108632402595000">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="5062108632402595000">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="6898329690939802290">"SBC"</item>
<item msgid="6839647709301342559">"AAC"</item>
<item msgid="7848030269621918608">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> ऑडियो"</item>
@@ -96,38 +96,38 @@
<item msgid="741805482892725657">"वैकल्पिक कोडेक अक्षम करें"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="3093023430402746802">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="3093023430402746802">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="8895532488906185219">"44.1 kHz"</item>
<item msgid="2909915718994807056">"48.0 kHz"</item>
<item msgid="3347287377354164611">"88.2 kHz"</item>
<item msgid="1234212100239985373">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="3214516120190965356">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="3214516120190965356">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="4482862757811638365">"44.1 kHz"</item>
<item msgid="354495328188724404">"48.0 kHz"</item>
<item msgid="7329816882213695083">"88.2 kHz"</item>
<item msgid="6967397666254430476">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="2684127272582591429">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="2684127272582591429">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="5618929009984956469">"16 बिट/नमूना"</item>
<item msgid="3412640499234627248">"24 बिट/नमूना"</item>
<item msgid="121583001492929387">"32 बिट/नमूना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="1081159789834584363">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="1081159789834584363">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="4726688794884191540">"16 बिट/नमूना"</item>
<item msgid="305344756485516870">"24 बिट/नमूना"</item>
<item msgid="244568657919675099">"32 बिट/नमूना"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="5226878858503393706">"सिस्टम से चुने जाने का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="5226878858503393706">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="4106832974775067314">"मोनो"</item>
<item msgid="5571632958424639155">"स्टीरियो"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="4118561796005528173">"सिस्टम चुनाव का उपयोग करें (डिफ़ॉल्ट)"</item>
+ <item msgid="4118561796005528173">"सिस्टम चयन का उपयोग करें (डिफ़ॉल्ट)"</item>
<item msgid="8900559293912978337">"मोनो"</item>
<item msgid="8883739882299884241">"स्टीरियो"</item>
</string-array>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 3a20d04d7ab2..5d512a84ef99 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -46,7 +46,7 @@
<string name="wifi_limited_connection" msgid="7717855024753201527">"सीमित कनेक्शन"</string>
<string name="wifi_status_no_internet" msgid="5784710974669608361">"इंटरनेट कनेक्शन नहीं है"</string>
<string name="wifi_status_sign_in_required" msgid="123517180404752756">"साइन इन करना ज़रूरी है"</string>
- <string name="wifi_ap_unable_to_handle_new_sta" msgid="5348824313514404541">"ऐक्सेस पॉइंट फ़िलहाल भरा हुआ है"</string>
+ <string name="wifi_ap_unable_to_handle_new_sta" msgid="5348824313514404541">"एक्सेस पॉइंट फ़िलहाल भरा हुआ है"</string>
<string name="connected_via_carrier" msgid="7583780074526041912">"%1$s के ज़रिए कनेक्ट"</string>
<string name="available_via_carrier" msgid="1469036129740799053">"%1$s के ज़रिए उपलब्ध"</string>
<string name="osu_opening_provider" msgid="5488997661548640424">"<xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g> खोला जा रहा है"</string>
@@ -68,7 +68,7 @@
<string name="bluetooth_pairing" msgid="1426882272690346242">"युग्‍मित कर रहा है…"</string>
<string name="bluetooth_connected_no_headset" msgid="616068069034994802">"जुड़ गया (फ़ोन के ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="3736431800395923868">"जुड़ गया (मीडिया ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_connected_no_map" msgid="3200033913678466453">"जुड़ गया (मैसेज का ऐक्सेस नहीं)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
+ <string name="bluetooth_connected_no_map" msgid="3200033913678466453">"जुड़ गया (मैसेज का एक्सेस नहीं)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_headset_no_a2dp" msgid="2047403011284187056">"जुड़ गया (फ़ोन या मीडिया ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_battery_level" msgid="5162924691231307748">"जुड़ गया, बैटरी का लेवल <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
<string name="bluetooth_connected_no_headset_battery_level" msgid="1610296229139400266">"जुड़ गया (फ़ोन के ऑडियो को छोड़कर), बैटरी का लेवल <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string>
@@ -88,7 +88,7 @@
<string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"संपर्क साझाकरण के लिए उपयोग करें"</string>
<string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"इंटरनेट कनेक्शन साझाकरण"</string>
<string name="bluetooth_profile_map" msgid="1019763341565580450">"लेख संदेश"</string>
- <string name="bluetooth_profile_sap" msgid="5764222021851283125">"सिम ऐक्सेस"</string>
+ <string name="bluetooth_profile_sap" msgid="5764222021851283125">"सिम एक्सेस"</string>
<string name="bluetooth_profile_a2dp_high_quality" msgid="5444517801472820055">"HD ऑडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="8510588052415438887">"HD ऑडियो"</string>
<string name="bluetooth_profile_hearing_aid" msgid="6680721080542444257">"सुनने में मदद करने वाले डिवाइस"</string>
@@ -200,7 +200,7 @@
<string name="development_settings_not_available" msgid="4308569041701535607">"यह उपयोगकर्ता, डेवलपर के लिए सेटिंग और टूल का इस्तेमाल नहीं कर सकता"</string>
<string name="vpn_settings_not_available" msgid="956841430176985598">"VPN सेटिंग इस उपयोगकर्ता के लिए उपलब्ध नहीं हैं"</string>
<string name="tethering_settings_not_available" msgid="6765770438438291012">"टेदरिंग सेटिंग इस उपयोगकर्ता के लिए उपलब्ध नहीं हैं"</string>
- <string name="apn_settings_not_available" msgid="7873729032165324000">"ऐक्सेस पॉइंट के नाम की सेटिंग इस उपयोगकर्ता के लिए मौजूद नहीं हैं"</string>
+ <string name="apn_settings_not_available" msgid="7873729032165324000">"एक्सेस पॉइंट के नाम की सेटिंग इस उपयोगकर्ता के लिए मौजूद नहीं हैं"</string>
<string name="enable_adb" msgid="7982306934419797485">"USB डीबग करना"</string>
<string name="enable_adb_summary" msgid="4881186971746056635">"डीबग मोड जब USB कनेक्‍ट किया गया हो"</string>
<string name="clear_adb_keys" msgid="4038889221503122743">"USB डीबग करने की मंज़ूरी रद्द करें"</string>
@@ -361,7 +361,7 @@
<string name="runningservices_settings_summary" msgid="854608995821032748">"इस समय चल रही सेवाओं को देखें और नियंत्रित करें"</string>
<string name="select_webview_provider_title" msgid="4628592979751918907">"वेबव्यू लागू करें"</string>
<string name="select_webview_provider_dialog_title" msgid="4370551378720004872">"वेबव्यू सेट करें"</string>
- <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"यह चुनाव अब मान्य नहीं है. दोबारा कोशिश करें."</string>
+ <string name="select_webview_provider_toast_text" msgid="5466970498308266359">"यह चयन अब मान्य नहीं है. पुनः प्रयास करें."</string>
<string name="convert_to_file_encryption" msgid="3060156730651061223">"फ़ाइल आधारित सुरक्षित करने के तरीके में बदलें"</string>
<string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"रूपांतरित करें..."</string>
<string name="convert_to_file_encryption_done" msgid="7859766358000523953">"फ़ाइल पहले से एन्क्रिप्ट की हुई है"</string>
@@ -414,7 +414,7 @@
<string name="disabled" msgid="9206776641295849915">"बंद किया गया"</string>
<string name="external_source_trusted" msgid="2707996266575928037">"अनुमति है"</string>
<string name="external_source_untrusted" msgid="2677442511837596726">"अनुमति नहीं है"</string>
- <string name="install_other_apps" msgid="6986686991775883017">"अनजान ऐप्लिकेशन इंस्टॉल करने का ऐक्सेस"</string>
+ <string name="install_other_apps" msgid="6986686991775883017">"अनजान ऐप्लिकेशन इंस्टॉल करने का एक्सेस"</string>
<string name="home" msgid="3256884684164448244">"सेटिंग का होम पेज"</string>
<string-array name="battery_labels">
<item msgid="8494684293649631252">"0%"</item>
diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml
index 7368f1d105f2..5cffafed6689 100644
--- a/packages/SettingsLib/res/values-hy/arrays.xml
+++ b/packages/SettingsLib/res/values-hy/arrays.xml
@@ -43,7 +43,7 @@
<item msgid="8937994881315223448">"Միացված է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ին"</item>
<item msgid="1330262655415760617">"Անջատված"</item>
<item msgid="7698638434317271902">"Անջատվում է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ից…"</item>
- <item msgid="197508606402264311">"Անջատված է"</item>
+ <item msgid="197508606402264311">"Անջատած է"</item>
<item msgid="8578370891960825148">"Անհաջող"</item>
<item msgid="5660739516542454527">"Արգելափակված"</item>
<item msgid="1805837518286731242">"Վատ ցանցից ժամանակավոր խուսափում"</item>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index a74b4ae3ae9e..bf587405c668 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -263,7 +263,7 @@
<string name="debug_view_attributes" msgid="6485448367803310384">"Միացնել ցուցադրման հատկանիշների ստուգումը"</string>
<string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Միշտ ակտիվացրած պահել բջջային տվյալները, նույնիսկ Wi‑Fi-ը միացրած ժամանակ (ցանցերի միջև արագ փոխարկման համար):"</string>
<string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Օգտագործել սարքակազմի արագացման միացումը, եթե հասանելի է"</string>
- <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB վրիպազերծումը:"</string>
+ <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB-ի վրիպազերծումը:"</string>
<string name="adb_warning_message" msgid="7316799925425402244">"USB վրիպազերծումը միայն ծրագրավորման նպատակների համար է: Օգտագործեք այն ձեր համակարգչից տվյալները ձեր սարք պատճենելու համար, առանց ծանուցման ձեր սարքի վրա ծրագրեր տեղադրելու և տվյալների մատյանը ընթերցելու համար:"</string>
<string name="adb_keys_warning_message" msgid="5659849457135841625">"Փակե՞լ USB-ի վրիպազերծման մուտքը` անջատելով այն բոլոր համակարգիչներից, որտեղ նախկինում թույլատրել էիք:"</string>
<string name="dev_settings_warning_title" msgid="7244607768088540165">"Ընդունե՞լ ծրագրավորման կարգավորումներ:"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index f1934c37369e..fd45cb0a1f3c 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -248,7 +248,7 @@
<string name="wifi_display_certification_summary" msgid="1155182309166746973">"ताररहित प्रदर्शन प्रमाणीकरणका लागि विकल्पहरू देखाउनुहोस्"</string>
<string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi लग स्तर बढाउनुहोस्, Wi-Fi चयनकर्तामा प्रति SSID RSSI देखाइन्छ"</string>
<string name="wifi_scan_throttling_summary" msgid="4461922728822495763">"ब्याट्रीको खपत कम गरी नेटवर्कको कार्यसम्पादनमा सुधार गर्दछ"</string>
- <string name="wifi_metered_label" msgid="4514924227256839725">"सशुल्क वाइफाइ"</string>
+ <string name="wifi_metered_label" msgid="4514924227256839725">"मिटर गरिएको जडान भनी चिन्ह लगाइएको"</string>
<string name="wifi_unmetered_label" msgid="6124098729457992931">"मिटर नगरिएको"</string>
<string name="select_logd_size_title" msgid="7433137108348553508">"लगर बफर आकारहरू"</string>
<string name="select_logd_size_dialog_title" msgid="1206769310236476760">"लग बफर प्रति लगर आकार चयन गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 57e690d46b23..cf442b73e721 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -159,7 +159,7 @@
<string name="tts_default_pitch_title" msgid="6135942113172488671">"ஒலித்திறன்"</string>
<string name="tts_default_pitch_summary" msgid="1944885882882650009">"உருவாக்கப்படும் பேச்சின் டோன் பாதிக்கப்படும்"</string>
<string name="tts_default_lang_title" msgid="8018087612299820556">"மொழி"</string>
- <string name="tts_lang_use_system" msgid="2679252467416513208">"அமைப்பின் மொழியைப் பயன்படுத்தவும்"</string>
+ <string name="tts_lang_use_system" msgid="2679252467416513208">"அமைப்பின் மொழியில்"</string>
<string name="tts_lang_not_selected" msgid="7395787019276734765">"மொழி தேர்ந்தெடுக்கப்படவில்லை"</string>
<string name="tts_default_lang_summary" msgid="5219362163902707785">"பேசப்படும் உரைக்கு மொழி சார்ந்த குரலை அமைக்கிறது"</string>
<string name="tts_play_example_title" msgid="7094780383253097230">"எடுத்துக்காட்டைக் கவனிக்கவும்"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 5c06c80a049c..4e2d6fa76ad0 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -201,7 +201,7 @@
<string name="vpn_settings_not_available" msgid="956841430176985598">"Cài đặt VPN không khả dụng cho người dùng này"</string>
<string name="tethering_settings_not_available" msgid="6765770438438291012">"Cài đặt chia sẻ kết nối không khả dụng cho người dùng này"</string>
<string name="apn_settings_not_available" msgid="7873729032165324000">"Cài đặt tên điểm truy cập không khả dụng cho người dùng này"</string>
- <string name="enable_adb" msgid="7982306934419797485">"Gỡ lỗi qua USB"</string>
+ <string name="enable_adb" msgid="7982306934419797485">"Gỡ lỗi USB"</string>
<string name="enable_adb_summary" msgid="4881186971746056635">"Bật chế độ gỡ lỗi khi kết nối USB"</string>
<string name="clear_adb_keys" msgid="4038889221503122743">"Thu hồi ủy quyền gỡ lỗi USB"</string>
<string name="bugreport_in_power" msgid="7923901846375587241">"Phím tắt báo cáo lỗi"</string>
@@ -263,7 +263,7 @@
<string name="debug_view_attributes" msgid="6485448367803310384">"Cho phép kiểm tra thuộc tính của chế độ xem"</string>
<string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Luôn bật dữ liệu di động ngay cả khi Wi-Fi đang hoạt động (để chuyển đổi mạng nhanh)."</string>
<string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Sử dụng tính năng tăng tốc phần cứng khi chia sẻ kết nối nếu có"</string>
- <string name="adb_warning_title" msgid="6234463310896563253">"Cho phép gỡ lỗi qua USB?"</string>
+ <string name="adb_warning_title" msgid="6234463310896563253">"Cho phép gỡ lỗi USB?"</string>
<string name="adb_warning_message" msgid="7316799925425402244">"Gỡ lỗi USB chỉ dành cho mục đích phát triển. Hãy sử dụng tính năng này để sao chép dữ liệu giữa máy tính và thiết bị của bạn, cài đặt ứng dụng trên thiết bị của bạn mà không thông báo và đọc dữ liệu nhật ký."</string>
<string name="adb_keys_warning_message" msgid="5659849457135841625">"Thu hồi quyền truy cập gỡ lỗi USB từ tất cả máy tính mà bạn đã ủy quyền trước đó?"</string>
<string name="dev_settings_warning_title" msgid="7244607768088540165">"Cho phép cài đặt phát triển?"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 99d48d3957b6..aac7fc3c927a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -34,11 +34,14 @@ import androidx.core.text.TextDirectionHeuristicsCompat;
import com.android.settingslib.R;
+import libcore.timezone.CountryTimeZones;
+import libcore.timezone.CountryTimeZones.TimeZoneMapping;
import libcore.timezone.TimeZoneFinder;
import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -387,7 +390,21 @@ public class ZoneGetter {
@VisibleForTesting
public List<String> lookupTimeZoneIdsByCountry(String country) {
- return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
+ final CountryTimeZones countryTimeZones =
+ TimeZoneFinder.getInstance().lookupCountryTimeZones(country);
+ if (countryTimeZones == null) {
+ return null;
+ }
+ final List<TimeZoneMapping> mappings = countryTimeZones.getTimeZoneMappings();
+ return extractTimeZoneIds(mappings);
+ }
+
+ private static List<String> extractTimeZoneIds(List<TimeZoneMapping> timeZoneMappings) {
+ final List<String> zoneIds = new ArrayList<>(timeZoneMappings.size());
+ for (TimeZoneMapping timeZoneMapping : timeZoneMappings) {
+ zoneIds.add(timeZoneMapping.timeZoneId);
+ }
+ return Collections.unmodifiableList(zoneIds);
}
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 0c49f635f5bd..1527de1a1d17 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -69,5 +69,6 @@ public class GlobalSettings {
Settings.Global.ZEN_DURATION,
Settings.Global.CHARGING_VIBRATION_ENABLED,
Settings.Global.AWARE_ALLOWED,
+ Settings.Global.NOTIFICATION_BUBBLES,
};
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 00b2563f559b..365923ea8643 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1114,6 +1114,9 @@ class SettingsProtoDumpUtil {
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
GlobalSettingsProto.Notification.SNOOZE_OPTIONS);
dumpSetting(s, p,
+ Settings.Global.NOTIFICATION_BUBBLES,
+ GlobalSettingsProto.Notification.BUBBLES);
+ dumpSetting(s, p,
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
GlobalSettingsProto.Notification.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS);
dumpSetting(s, p,
@@ -2225,7 +2228,7 @@ class SettingsProtoDumpUtil {
Settings.Secure.NOTIFICATION_BADGING,
SecureSettingsProto.Notification.BADGING);
dumpSetting(s, p,
- Settings.Secure.NOTIFICATION_BUBBLES,
+ Settings.Global.NOTIFICATION_BUBBLES,
SecureSettingsProto.Notification.BUBBLES);
dumpSetting(s, p,
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 4d71e72b59d6..32fc7ff978cd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3186,7 +3186,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 182;
+ private static final int SETTINGS_VERSION = 183;
private final int mUserId;
@@ -4203,19 +4203,7 @@ public class SettingsProvider extends ContentProvider {
if (currentVersion == 173) {
// Version 173: Set the default value for Secure Settings: NOTIFICATION_BUBBLES
-
- final SettingsState secureSettings = getSecureSettingsLocked(userId);
-
- final Setting bubblesSetting = secureSettings.getSettingLocked(
- Secure.NOTIFICATION_BUBBLES);
-
- if (bubblesSetting.isNull()) {
- secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
- getContext().getResources().getBoolean(
- R.bool.def_notification_bubbles) ? "1" : "0", null,
- true, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
+ // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
currentVersion = 174;
}
@@ -4339,14 +4327,9 @@ public class SettingsProvider extends ContentProvider {
if (currentVersion == 179) {
// Version 178: Reset the default for Secure Settings: NOTIFICATION_BUBBLES
// This is originally set in version 173, however, the default value changed
- // so this step is to ensure the value is updated to the correct defaulte
- final SettingsState secureSettings = getSecureSettingsLocked(userId);
-
- secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
- getContext().getResources().getBoolean(
- R.bool.def_notification_bubbles) ? "1" : "0", null,
- true, SettingsState.SYSTEM_PACKAGE_NAME);
+ // so this step is to ensure the value is updated to the correct default.
+ // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
currentVersion = 180;
}
@@ -4400,6 +4383,20 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 182;
}
+ if (currentVersion == 182) {
+ // Remove secure bubble settings.
+ getSecureSettingsLocked(userId).deleteSettingLocked(
+ Secure.NOTIFICATION_BUBBLES);
+
+ // Add global bubble settings.
+ getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
+ getContext().getResources().getBoolean(
+ R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
+ true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+ currentVersion = 183;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 12ca92f02e7a..57e22db6a55c 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -30,6 +30,7 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.provider.Settings;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
@@ -47,6 +48,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -270,5 +272,17 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
}
return result;
}
+
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ for (Object[] resultRow : RESULT_ROWS) {
+ if (Objects.equals(request, resultRow[0])) {
+ final Bundle res = new Bundle();
+ res.putString("value", String.valueOf(resultRow[1]));
+ return res;
+ }
+ }
+ return Bundle.EMPTY;
+ }
}
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index 183f5997a6b5..d67a9bc97cb8 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -33,6 +33,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -693,6 +694,7 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
}
@Test
+ @Ignore("b/140250974")
public void testLocationModeChanges_viaFrontEndApi() throws Exception {
setStringViaFrontEndApiSetting(
SETTING_TYPE_SECURE,
@@ -735,6 +737,7 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
}
@Test
+ @Ignore("b/140250974")
public void testLocationProvidersAllowed_disableProviders() throws Exception {
setStringViaFrontEndApiSetting(
SETTING_TYPE_SECURE,
@@ -766,6 +769,7 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
}
@Test
+ @Ignore("b/140250974")
public void testLocationProvidersAllowed_enableAndDisable() throws Exception {
setStringViaFrontEndApiSetting(
SETTING_TYPE_SECURE,
@@ -788,6 +792,7 @@ public class SettingsProviderTest extends BaseSettingsProviderTest {
}
@Test
+ @Ignore("b/140250974")
public void testLocationProvidersAllowedLocked_invalidInput() throws Exception {
setStringViaFrontEndApiSetting(
SETTING_TYPE_SECURE,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4c52b1324781..3e5d72077f8b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -114,6 +114,7 @@ android_library {
"androidx.lifecycle_lifecycle-extensions",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
+ "iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
"metrics-helper-lib",
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
index 488c4657333f..de2ee9e04b30 100644
--- a/packages/SystemUI/docs/physics-animation-layout.md
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -1,7 +1,11 @@
# 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.
+**PhysicsAnimationLayout** works with implementations of **PhysicsAnimationController** to configure and run physics-based animations for each of its child views. During the initial construction of the animations, the layout queries the controller for basic configuration settings such as which properties to animate, which animations to chain together, and the default physics parameters to use.
+
+Once the animations are built, the controller can access **PhysicsPropertyAnimator** instances to run them. The animator behaves similarly to the familiar `ViewPropertyAnimator`, with the ability to animate `alpha`, `translation`, and `scale` values. It also supports additional functionality such as `followAnimatedTargetAlongPath` for more advanced motion.
+
+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.
@@ -27,7 +31,7 @@ Returns a SpringForce instance to use for animations of the given property. This
### Animation Control Methods
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```, ```onChildRemoved```, and ```setChildVisibility``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out/visible/gone. 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 can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`.
+In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`.
#### PhysicsPropertyAnimator
@@ -36,9 +40,14 @@ Like `ViewPropertyAnimator`, `PhysicsPropertyAnimator` provides the following me
- `translationX/Y/Z(float)`
- `scaleX/Y(float)`
+...as well as shortcut methods to reduce the amount of boilerplate code needed for common use cases:
+- `position(float, float, Runnable…)`, which starts translationX and translationY animations, and calls the provided callbacks only when both animations have completed.
+- `followAnimatedTargetAlongPath(Path, int, TimeInterpolator)`, which animates a ‘target’ point along the given path using a traditional Animator. As the target moves, the translationX/Y physics animations are updated to follow the target, similarly to how they might follow a touch event location. This results in the view roughly following the path, but with natural motion that takes momentum into account. For example, if a path makes a 90 degree turn to the right, the physics animations will cause the view to curve naturally towards the new trajectory.
+
It also provides the following configuration methods:
- `withStartDelay(int)`, for starting the animation after a given delay.
- `withStartVelocity(float)`, for starting the animation with the given start velocity.
+- `withStiffness(float)` and `withDampingRatio(float)`, for overriding the default physics param values returned by the controller’s getSpringForce method.
- `withPositionStartVelocities(float, float)`, for setting specific start velocities for TRANSLATION_X and TRANSLATION_Y, since these typically differ.
- `start(Runnable)`, to start the animation, with an optional end action to call when the animations for every property (including chained animations) have completed.
@@ -61,8 +70,7 @@ The animator has additional functionality to reduce the amount of boilerplate re
- Often, animations will set starting values for properties before the animation begins. Property methods like `translationX` have an overloaded variant: `translationX(from, to)`. When `start()` is called, the animation will set the view's translationX property to `from` before beginning the animation to `to`.
- We may want to use different end actions for each property. For example, if we're animating a view to the bottom of the screen, and also fading it out, we might want to perform an action as soon as the fade out is complete. We can use `alpha(to, endAction)`, which will call endAction as soon as the alpha animation is finished. A special case is `position(x, y, endAction)`, where the endAction is called when both translationX and translationY animations have completed.
-
-`PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
+- `PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
##### Examples
Spring the stack of bubbles (whose animations are chained) to the bottom of the screen, shrinking them to 50% size. Once the first bubble is done shrinking, begin fading them out, and then remove them all from the parent once all bubbles have faded out:
@@ -93,16 +101,22 @@ animationsForChildrenFromIndex(1, (index, anim) -> anim.translationX((index - 1)
.startAll(removeFirstView);
```
-## PhysicsAnimationLayout
-The layout itself is a FrameLayout descendant with a few extra methods:
+Move a view up along the left side of the screen, and then to the top right of the screen (assume a view that is currently halfway down the left side of the screen). When the translation animations have finished following the target, call a callback:
-```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.
+```
+Path path = new Path();
+path.moveTo(view.getTranslationX(), view.getTranslationY());
+path.lineTo(view.getTranslationX(), 0);
+path.lineTo(mScreenWidth, 0);
+animationForChild(view)
+ .followAnimatedTargetAlongPath(path, 100, new LinearInterpolator())
+ .start(callbackAfterFollowingFinished);
+```
-```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
-Sets an end listener that is called when all animations on the given property have ended.
+## PhysicsAnimationLayout
+The layout itself is a FrameLayout descendant with a few extra methods:
-```setMaxRenderedChildren(int max)```
-Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
+```setActiveController(PhysicsAnimationController controller)```
+Sets the given controller as the active controller for the layout. This causes the layout to construct or reconfigure the physics animations according to the new controller’s configuration methods, and halt any in-progress animations.
-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
+Only the currently active controller is allowed to start animations. If a different controller is set as the active controller, the previous controller will no longer be able to start animations. Attempts to do so will have no effect. This is to ensure that multiple controllers aren’t updating animations at the same time, which can cause undefined behavior. \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 3adc2792ef10..0d88a2074efa 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -41,7 +41,7 @@
<string name="keyguard_low_battery" msgid="9218432555787624490">"Připojte dobíjecí zařízení."</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"Klávesy odemknete stisknutím tlačítka nabídky."</string>
<string name="keyguard_network_locked_message" msgid="6743537524631420759">"Síť je blokována"</string>
- <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"Chybí SIM karta"</string>
+ <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"Není vložena SIM karta"</string>
<string name="keyguard_missing_sim_message" product="tablet" msgid="4550152848200783542">"V tabletu není SIM karta."</string>
<string name="keyguard_missing_sim_message" product="default" msgid="6585414237800161146">"V telefonu není SIM karta."</string>
<string name="keyguard_missing_sim_instructions" msgid="7350295932015220392">"Vložte SIM kartu."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 9c9de22b96d9..22c4c48a7e37 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -37,7 +37,7 @@
<string name="keyguard_plugged_in_wireless" msgid="8404159927155454732">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ بی‌سیم"</string>
<string name="keyguard_plugged_in" msgid="3161102098900158923">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ شدن"</string>
<string name="keyguard_plugged_in_charging_fast" msgid="3684592786276709342">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ سریع"</string>
- <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • آهسته‌آهسته شارژ می‌شود"</string>
+ <string name="keyguard_plugged_in_charging_slowly" msgid="509533586841478405">"<xliff:g id="PERCENTAGE">%s</xliff:g> • درحال شارژ آهسته"</string>
<string name="keyguard_low_battery" msgid="9218432555787624490">"شارژر را وصل کنید."</string>
<string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"برای باز کردن قفل روی «منو» فشار دهید."</string>
<string name="keyguard_network_locked_message" msgid="6743537524631420759">"شبکه قفل شد"</string>
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
index 1661bb22d148..8c7e82f82186 100644
--- a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
+++ b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
@@ -24,4 +24,5 @@
android:width="1dp"
android:color="#66FFFFFF" />
+ <solid android:color="#B3000000" />
</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index 245177c8461b..ca085b69c35d 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -20,6 +20,13 @@
android:layout_height="@dimen/pip_dismiss_gradient_height"
android:layout_gravity="bottom|center_horizontal">
+ <FrameLayout
+ android:id="@+id/bubble_dismiss_circle"
+ android:layout_width="@dimen/bubble_dismiss_encircle_size"
+ android:layout_height="@dimen/bubble_dismiss_encircle_size"
+ android:layout_gravity="center"
+ android:background="@drawable/bubble_dismiss_circle" />
+
<LinearLayout
android:id="@+id/bubble_dismiss_icon_container"
android:layout_width="wrap_content"
@@ -38,29 +45,5 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/bubble_dismiss_icon" />
-
- <TextView
- android:id="@+id/bubble_dismiss_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="9dp"
- android:layout_marginBottom="9dp"
- android:layout_marginLeft="8dp"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
- android:textColor="@android:color/white"
- android:shadowColor="@android:color/black"
- android:shadowDx="-1"
- android:shadowDy="1"
- android:shadowRadius="0.01"
- android:text="@string/bubble_dismiss_text" />
-
</LinearLayout>
-
- <FrameLayout
- android:id="@+id/bubble_dismiss_circle"
- android:layout_width="@dimen/bubble_dismiss_encircle_size"
- android:layout_height="@dimen/bubble_dismiss_encircle_size"
- android:layout_gravity="center"
- android:alpha="0"
- android:background="@drawable/bubble_dismiss_circle" />
</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index a8eb2914b0b2..e2dea45e3406 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -24,7 +24,6 @@
android:id="@+id/bubble_image"
android:layout_width="@dimen/individual_bubble_size"
android:layout_height="@dimen/individual_bubble_size"
- android:padding="@dimen/bubble_view_padding"
android:clipToPadding="false"/>
</com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index a91493003bb5..9716a00a7f72 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -44,7 +44,7 @@
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.statusbar.ScrimView
- android:id="@+id/scrim_behind"
+ android:id="@+id/scrim_for_bubble"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
@@ -56,6 +56,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+ <com.android.systemui.statusbar.ScrimView
+ android:id="@+id/scrim_behind"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ sysui:ignoreRightInset="true"
+ />
+
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 36745648b051..dcd31bda79b3 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -329,7 +329,7 @@
<string name="quick_settings_location_off_label" msgid="7464544086507331459">"Вызначэнне месцазнаходжання адключана"</string>
<string name="quick_settings_media_device_label" msgid="1302906836372603762">"Мультымедыйная прылада"</string>
<string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string>
- <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Толькі экстранныя выклікі"</string>
+ <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Толькі экстраныя выклікі"</string>
<string name="quick_settings_settings_label" msgid="5326556592578065401">"Налады"</string>
<string name="quick_settings_time_label" msgid="4635969182239736408">"Час"</string>
<string name="quick_settings_user_label" msgid="5238995632130897840">"Я"</string>
@@ -423,7 +423,7 @@
<string name="keyguard_indication_charging_time_wireless" msgid="6959284458466962592">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе бесправадная зарадка (да поўнага зараду засталося <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
<string name="keyguard_indication_charging_time" msgid="2056340799276374421">"Ідзе зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, яшчэ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
<string name="keyguard_indication_charging_time_fast" msgid="7767562163577492332">"Ідзе хуткая зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, яшчэ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
- <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"Ідзе павольная зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> да канца)"</string>
+ <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"Ідзе павольная зарадка (<xliff:g id="PERCENTAGE">%2$s</xliff:g>, яшчэ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Перайсці да іншага карыстальніка"</string>
<string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Перайсці да іншага карыстальніка, бягучы карыстальнік <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
<string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Бягучы карыстальнік <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 6b7561bdcd3b..5141b57ec295 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -232,9 +232,9 @@
<string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"বিমান মোড চালু হয়েছে।"</string>
<string name="accessibility_quick_settings_dnd_none_on" msgid="2960643943620637020">"সম্পূর্ণ নীরব"</string>
<string name="accessibility_quick_settings_dnd_alarms_on" msgid="3357131899365865386">"শুধুমাত্র অ্যালার্ম"</string>
- <string name="accessibility_quick_settings_dnd" msgid="5555155552520665891">"বিরক্ত করবে না।"</string>
- <string name="accessibility_quick_settings_dnd_changed_off" msgid="2757071272328547807">"\'বিরক্ত করবে না\' বন্ধ আছে।"</string>
- <string name="accessibility_quick_settings_dnd_changed_on" msgid="6808220653747701059">"\'বিরক্ত করবে না\' চালু করা হয়েছে।"</string>
+ <string name="accessibility_quick_settings_dnd" msgid="5555155552520665891">"বিরক্ত করবেন না।"</string>
+ <string name="accessibility_quick_settings_dnd_changed_off" msgid="2757071272328547807">"\'বিরক্ত করবেন না\' বন্ধ আছে।"</string>
+ <string name="accessibility_quick_settings_dnd_changed_on" msgid="6808220653747701059">"\'বিরক্ত করবেন না\' চালু করা হয়েছে।"</string>
<string name="accessibility_quick_settings_bluetooth" msgid="6341675755803320038">"ব্লুটুথ"</string>
<string name="accessibility_quick_settings_bluetooth_off" msgid="2133631372372064339">"ব্লুটুথ বন্ধ আছে।"</string>
<string name="accessibility_quick_settings_bluetooth_on" msgid="7681999166216621838">"ব্লুটুথ চালু আছে।"</string>
@@ -299,7 +299,7 @@
<string name="start_dreams" msgid="5640361424498338327">"স্ক্রিন সেভার"</string>
<string name="ethernet_label" msgid="7967563676324087464">"ইথারনেট"</string>
<string name="quick_settings_header_onboarding_text" msgid="8030309023792936283">"আরও বিকল্পের জন্য আইকনগুলি টাচ করে ধরে থাকুন"</string>
- <string name="quick_settings_dnd_label" msgid="7112342227663678739">"বিরক্ত করবে না"</string>
+ <string name="quick_settings_dnd_label" msgid="7112342227663678739">"বিরক্ত করবেন না"</string>
<string name="quick_settings_dnd_priority_label" msgid="483232950670692036">"শুধুমাত্র অগ্রাধিকার"</string>
<string name="quick_settings_dnd_alarms_label" msgid="2559229444312445858">"শুধুমাত্র অ্যালার্মগুলি"</string>
<string name="quick_settings_dnd_none_label" msgid="5025477807123029478">"একদম নিরব"</string>
@@ -461,7 +461,7 @@
<string name="manage_notifications_text" msgid="2386728145475108753">"পরিচালনা করুন"</string>
<string name="notification_section_header_gentle" msgid="4372438504154095677">"নীরব বিজ্ঞপ্তি"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"সব নীরব বিজ্ঞপ্তি মুছুন"</string>
- <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"\'বিরক্ত করবে না\' দিয়ে বিজ্ঞপ্তি পজ করা হয়েছে"</string>
+ <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"\'বিরক্ত করবেন না\' দিয়ে বিজ্ঞপ্তি পজ করা হয়েছে"</string>
<string name="media_projection_action_text" msgid="8470872969457985954">"এখন শুরু করুন"</string>
<string name="empty_shade_text" msgid="708135716272867002">"কোনো বিজ্ঞপ্তি নেই"</string>
<string name="profile_owned_footer" msgid="8021888108553696069">"প্রোফাইল পর্যবেক্ষণ করা হতে পারে"</string>
@@ -737,9 +737,9 @@
<string name="keyboard_shortcut_group_applications_youtube" msgid="6555453761294723317">"YouTube"</string>
<string name="keyboard_shortcut_group_applications_calendar" msgid="9043614299194991263">"ক্যালেন্ডার"</string>
<string name="tuner_full_zen_title" msgid="4540823317772234308">"ভলিউম নিয়ন্ত্রণ সহ দেখান"</string>
- <string name="volume_and_do_not_disturb" msgid="1750270820297253561">"বিরক্ত করবে না"</string>
+ <string name="volume_and_do_not_disturb" msgid="1750270820297253561">"বিরক্ত করবেন না"</string>
<string name="volume_dnd_silent" msgid="4363882330723050727">"ভলিউম বোতামের শর্টকাট"</string>
- <string name="volume_up_silent" msgid="7545869833038212815">"ভলিউম বাড়িয়ে \'বিরক্ত করবে না\' মোড থেকে বেরিয়ে আসুন"</string>
+ <string name="volume_up_silent" msgid="7545869833038212815">"ভলিউম বাড়িয়ে \'বিরক্ত করবেন না\' মোড থেকে বেরিয়ে আসুন"</string>
<string name="battery" msgid="7498329822413202973">"ব্যাটারি"</string>
<string name="clock" msgid="7416090374234785905">"ঘড়ি"</string>
<string name="headset" msgid="4534219457597457353">"হেডসেট"</string>
@@ -883,10 +883,10 @@
<string name="mobile_carrier_text_format" msgid="3241721038678469804">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
<string name="wifi_is_off" msgid="1838559392210456893">"ওয়াই ফাই বন্ধ আছে"</string>
<string name="bt_is_off" msgid="2640685272289706392">"ব্লুটুথ বন্ধ আছে"</string>
- <string name="dnd_is_off" msgid="6167780215212497572">"বিরক্ত করবে না বিকল্পটি বন্ধ আছে"</string>
- <string name="qs_dnd_prompt_auto_rule" msgid="862559028345233052">"বিরক্ত করবে না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
- <string name="qs_dnd_prompt_app" msgid="7978037419334156034">"বিরক্ত করবে না বিকল্পটি একটি অ্যাপ <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
- <string name="qs_dnd_prompt_auto_rule_app" msgid="2599343675391111951">"বিরক্ত করবে না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম বা অ্যাপের দ্বারা চালু করা হয়েছে।"</string>
+ <string name="dnd_is_off" msgid="6167780215212497572">"বিরক্ত করবেন না বিকল্পটি বন্ধ আছে"</string>
+ <string name="qs_dnd_prompt_auto_rule" msgid="862559028345233052">"বিরক্ত করবেন না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
+ <string name="qs_dnd_prompt_app" msgid="7978037419334156034">"বিরক্ত করবেন না বিকল্পটি একটি অ্যাপ <xliff:g id="ID_1">%s</xliff:g> এর দ্বারা চালু করা হয়েছে।"</string>
+ <string name="qs_dnd_prompt_auto_rule_app" msgid="2599343675391111951">"বিরক্ত করবেন না বিকল্পটি একটি স্বয়ংক্রিয় নিয়ম বা অ্যাপের দ্বারা চালু করা হয়েছে।"</string>
<string name="qs_dnd_until" msgid="3469471136280079874">"<xliff:g id="ID_1">%s</xliff:g> পর্যন্ত"</string>
<string name="qs_dnd_keep" msgid="1825009164681928736">"রাখুন"</string>
<string name="qs_dnd_replace" msgid="8019520786644276623">"বদলে দিন"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 87aaa94d1dd3..abea52a0adf8 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -194,7 +194,7 @@
<string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Sdílené připojení přes Bluetooth."</string>
<string name="accessibility_airplane_mode" msgid="834748999790763092">"Režim Letadlo."</string>
<string name="accessibility_vpn_on" msgid="5993385083262856059">"VPN je zapnuto."</string>
- <string name="accessibility_no_sims" msgid="3957997018324995781">"Chybí SIM karta"</string>
+ <string name="accessibility_no_sims" msgid="3957997018324995781">"Není vložena SIM karta"</string>
<string name="carrier_network_change_mode" msgid="8149202439957837762">"Probíhá změna sítě operátora"</string>
<string name="accessibility_battery_details" msgid="7645516654955025422">"Otevřít podrobnosti o baterii"</string>
<string name="accessibility_battery_level" msgid="7451474187113371965">"Stav baterie: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index e8e4232063e3..137b780fee2a 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -417,7 +417,7 @@
<string name="keyguard_indication_charging_time_wireless" msgid="6959284458466962592">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • En recharge sans fil (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à la recharge complète)"</string>
<string name="keyguard_indication_charging_time" msgid="2056340799276374421">"En recharge : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à charge complète)"</string>
<string name="keyguard_indication_charging_time_fast" msgid="7767562163577492332">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à ch. comp.)"</string>
- <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"Recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>)"</string>
+ <string name="keyguard_indication_charging_time_slowly" msgid="3769655133567307069">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> (<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> jusqu\'à ch. comp.)"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Changer d\'utilisateur (utilisateur actuel <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>)"</string>
<string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 1a15cf705d91..f90f494fe3a2 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -314,7 +314,7 @@
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="4930931771490695395">"શ્રવણ યંત્રો"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="4551281899312150640">"ચાલુ કરી રહ્યાં છીએ…"</string>
<string name="quick_settings_brightness_label" msgid="6968372297018755815">"તેજ"</string>
- <string name="quick_settings_rotation_unlocked_label" msgid="7305323031808150099">"ઑટો રોટેટ"</string>
+ <string name="quick_settings_rotation_unlocked_label" msgid="7305323031808150099">"આપમેળે ફેરવો"</string>
<string name="accessibility_quick_settings_rotation" msgid="4231661040698488779">"સ્ક્રીનને આપમેળે ફેરવો"</string>
<string name="accessibility_quick_settings_rotation_value" msgid="8187398200140760213">"<xliff:g id="ID_1">%s</xliff:g> મોડ"</string>
<string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"પરિભ્રમણ લૉક થયું"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 90e78e85ed19..5a8c5dc68bdf 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -36,4 +36,8 @@
<dimen name="volume_tool_tip_right_margin">136dp</dimen>
<dimen name="volume_tool_tip_top_margin">12dp</dimen>
+
+ <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
+ value in landscape since we have limited vertical space-->
+ <dimen name="bubble_padding_top">4dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index e9b150c1a66e..3760007b2291 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -250,9 +250,9 @@
<string name="accessibility_quick_settings_close" msgid="3115847794692516306">"ପ୍ୟାନେଲ୍ ବନ୍ଦ କରନ୍ତୁ।"</string>
<string name="accessibility_quick_settings_more_time" msgid="3659274935356197708">"ଅଧିକ ସମୟ।"</string>
<string name="accessibility_quick_settings_less_time" msgid="2404728746293515623">"କମ୍ ସମୟ।"</string>
- <string name="accessibility_quick_settings_flashlight_off" msgid="4936432000069786988">"ଫ୍ଲାସ୍‍ଲାଇଟ୍ ବନ୍ଦ ଅଛି।"</string>
+ <string name="accessibility_quick_settings_flashlight_off" msgid="4936432000069786988">"ଫ୍ଲାଶ୍‌ଲାଇଟ୍ ଅଫ୍ ଅଛି।"</string>
<string name="accessibility_quick_settings_flashlight_unavailable" msgid="8012811023312280810">"ଟର୍ଚ୍ଚ ଲାଇଟ୍‍ ଅନୁପଲବ୍ଧ।"</string>
- <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"ଫ୍ଲାସ୍‍ଲାଇଟ୍ ଚାଲୁଅଛି।"</string>
+ <string name="accessibility_quick_settings_flashlight_on" msgid="2003479320007841077">"ଫ୍ଲାଶ୍‌ଲାଇଟ୍ ଅନ୍ ଅଛି।"</string>
<string name="accessibility_quick_settings_flashlight_changed_off" msgid="3303701786768224304">"ଟର୍ଚ୍ଚ ଲାଇଟ୍ ବନ୍ଦ ଅଛି।"</string>
<string name="accessibility_quick_settings_flashlight_changed_on" msgid="6531793301533894686">"ଟର୍ଚ୍ଚ ଲାଇଟ୍ ଅନ୍ ଅଛି।"</string>
<string name="accessibility_quick_settings_color_inversion_changed_off" msgid="4406577213290173911">"ରଙ୍ଗ ବିପରୀତିକରଣକୁ ବନ୍ଦ କରିଦିଆଗଲା।"</string>
@@ -362,7 +362,7 @@
<item quantity="one">%d ଡିଭାଇସ୍</item>
</plurals>
<string name="quick_settings_notifications_label" msgid="4818156442169154523">"ବିଜ୍ଞପ୍ତି"</string>
- <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"ଫ୍ଲାସ୍‍ଲାଇଟ୍"</string>
+ <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"ଫ୍ଲାଶ୍‍ଲାଇଟ"</string>
<string name="quick_settings_cellular_detail_title" msgid="3661194685666477347">"ମୋବାଇଲ୍‌ ଡାଟା"</string>
<string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"ଡାଟାର ବ୍ୟବହାର"</string>
<string name="quick_settings_cellular_detail_remaining_data" msgid="722715415543541249">"ଅବଶିଷ୍ଟ ଡାଟା"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d3caa2b2c75d..6f38b18693d3 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -55,11 +55,11 @@
<string name="label_view" msgid="6304565553218192990">"Xem"</string>
<string name="always_use_device" msgid="4015357883336738417">"Luôn mở <xliff:g id="APPLICATION">%1$s</xliff:g> khi kết nối <xliff:g id="USB_DEVICE">%2$s</xliff:g>"</string>
<string name="always_use_accessory" msgid="3257892669444535154">"Luôn mở <xliff:g id="APPLICATION">%1$s</xliff:g> khi kết nối <xliff:g id="USB_ACCESSORY">%2$s</xliff:g>"</string>
- <string name="usb_debugging_title" msgid="4513918393387141949">"Cho phép gỡ lỗi qua USB?"</string>
+ <string name="usb_debugging_title" msgid="4513918393387141949">"Cho phép gỡ lỗi USB?"</string>
<string name="usb_debugging_message" msgid="2220143855912376496">"Tệp tham chiếu khóa RSA của máy tính là:\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="303335496705863070">"Luôn cho phép từ máy tính này"</string>
<string name="usb_debugging_allow" msgid="2272145052073254852">"Cho phép"</string>
- <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"Không cho phép chế độ gỡ lỗi qua USB"</string>
+ <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"Tính năng gỡ lỗi USB không được phép"</string>
<string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Người dùng hiện đã đăng nhập vào thiết bị này không thể bật tính năng gỡ lỗi USB. Để sử dụng tính năng này, hãy chuyển sang người dùng chính."</string>
<string name="usb_contaminant_title" msgid="206854874263058490">"Đã tắt cổng USB"</string>
<string name="usb_contaminant_message" msgid="7379089091591609111">"Để bảo vệ thiết bị của bạn khỏi chất lỏng hay mảnh vỡ, cổng USB sẽ tắt và không phát hiện được bất kỳ phụ kiện nào.\n\nBạn sẽ nhận được thông báo khi có thể sử dụng lại cổng USB."</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6bacf702cd64..03e31cdc7ee6 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -264,8 +264,8 @@
<string name="accessibility_quick_settings_work_mode_on" msgid="7650588553988014341">"工作模式已開啟。"</string>
<string name="accessibility_quick_settings_work_mode_changed_off" msgid="5605534876107300711">"工作模式已關閉。"</string>
<string name="accessibility_quick_settings_work_mode_changed_on" msgid="249840330756998612">"工作模式已開啟。"</string>
- <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"數據節省模式已關閉。"</string>
- <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"數據節省模式已開啟。"</string>
+ <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Data Saver 已關閉。"</string>
+ <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Data Saver 已開啟。"</string>
<string name="accessibility_quick_settings_sensor_privacy_changed_off" msgid="5152819588955163090">"已關閉感應器隱私設定。"</string>
<string name="accessibility_quick_settings_sensor_privacy_changed_on" msgid="529705259565826355">"已開啟感應器隱私設定。"</string>
<string name="accessibility_brightness" msgid="8003681285547803095">"螢幕亮度"</string>
@@ -747,8 +747,8 @@
<string name="accessibility_status_bar_headphones" msgid="9156307120060559989">"已與耳機連線"</string>
<string name="accessibility_status_bar_headset" msgid="8666419213072449202">"已與耳機連線"</string>
<string name="data_saver" msgid="5037565123367048522">"數據節省模式"</string>
- <string name="accessibility_data_saver_on" msgid="8454111686783887148">"數據節省模式已開啟"</string>
- <string name="accessibility_data_saver_off" msgid="8841582529453005337">"數據節省模式已關閉"</string>
+ <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Data Saver 已開啟"</string>
+ <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Data Saver 已關閉"</string>
<string name="switch_bar_on" msgid="1142437840752794229">"開啟"</string>
<string name="switch_bar_off" msgid="8803270596930432874">"關閉"</string>
<string name="nav_bar" msgid="1993221402773877607">"導覽列"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index be815e13e68e..2e1799168b5d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1102,18 +1102,23 @@
<dimen name="bubble_flyout_pointer_size">6dp</dimen>
<!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
<dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
- <!-- Padding around a collapsed bubble -->
- <dimen name="bubble_view_padding">0dp</dimen>
- <!-- Padding between bubbles when displayed in expanded state -->
- <dimen name="bubble_padding">8dp</dimen>
+ <!-- Padding between status bar and bubbles when displayed in expanded state -->
+ <dimen name="bubble_padding_top">16dp</dimen>
<!-- Size of individual bubbles. -->
- <dimen name="individual_bubble_size">52dp</dimen>
+ <dimen name="individual_bubble_size">60dp</dimen>
+ <!-- Size of bubble icon bitmap. -->
+ <dimen name="bubble_icon_bitmap_size">52dp</dimen>
+ <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
+ <dimen name="bubble_touch_padding">12dp</dimen>
<!-- Size of the circle around the bubbles when they're in the dismiss target. -->
- <dimen name="bubble_dismiss_encircle_size">56dp</dimen>
+ <dimen name="bubble_dismiss_encircle_size">52dp</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 -->
<dimen name="bubble_expanded_view_padding">4dp</dimen>
+ <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
+ a slight touch slop around the expanded view. -->
+ <dimen name="bubble_expanded_view_slop">8dp</dimen>
<!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
<dimen name="bubble_expanded_default_height">180dp</dimen>
<!-- Height of the triangle that points to the expanded bubble -->
@@ -1122,10 +1127,8 @@
<dimen name="bubble_pointer_width">6dp</dimen>
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
- <!-- Height of the header within the expanded view. -->
- <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>
+ <!-- Height of button allowing users to adjust settings for bubbles. -->
+ <dimen name="bubble_settings_size">48dp</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. -->
@@ -1138,12 +1141,10 @@
<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 offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
+ <dimen name="bubble_stack_offscreen">9dp</dimen>
<!-- How far down the screen the stack starts. -->
- <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
- <!-- Size of image buttons in the bubble header -->
- <dimen name="bubble_header_icon_size">48dp</dimen>
+ <dimen name="bubble_stack_starting_offset_y">96dp</dimen>
<!-- Space between the pointer triangle and the bubble expanded view -->
<dimen name="bubble_pointer_margin">8dp</dimen>
<!-- Height of the permission prompt shown with bubbles -->
@@ -1152,6 +1153,7 @@
snap to the dismiss target. -->
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 66f19495dfa6..372718139886 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -137,6 +137,7 @@
<item type="id" name="scale_x_dynamicanimation_tag"/>
<item type="id" name="scale_y_dynamicanimation_tag"/>
<item type="id" name="physics_animator_tag"/>
+ <item type="id" name="target_animator_tag" />
<!-- Global Actions Menu -->
<item type="id" name="global_actions_view" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 77571613f6af..5ddf89c08887 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -72,6 +72,13 @@ public abstract class TaskStackChangeListener {
*/
public void onSingleTaskDisplayDrawn(int displayId) { }
+ /**
+ * Called when the last task is removed from a display which can only contain one task.
+ *
+ * @param displayId the id of the display from which the window is removed.
+ */
+ public void onSingleTaskDisplayEmpty(int displayId) {}
+
public void onTaskProfileLocked(int taskId, int userId) { }
public void onTaskCreated(int taskId, ComponentName componentName) { }
public void onTaskRemoved(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index a7f4396fabed..820057a168a0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -209,6 +209,12 @@ public class TaskStackChangeListeners extends TaskStackListener {
}
@Override
+ public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+ mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+ 0 /* unused */).sendToTarget();
+ }
+
+ @Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
mHandler.obtainMessage(H.ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
}
@@ -240,6 +246,7 @@ public class TaskStackChangeListeners extends TaskStackListener {
private static final int ON_SINGLE_TASK_DISPLAY_DRAWN = 19;
private static final int ON_TASK_DISPLAY_CHANGED = 20;
private static final int ON_TASK_LIST_UPDATED = 21;
+ private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 22;
public H(Looper looper) {
@@ -382,6 +389,13 @@ public class TaskStackChangeListeners extends TaskStackListener {
}
break;
}
+ case ON_SINGLE_TASK_DISPLAY_EMPTY: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
+ msg.arg1);
+ }
+ break;
+ }
case ON_TASK_DISPLAY_CHANGED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onTaskDisplayChanged(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 39617ecd2770..45142b0bdc79 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -141,11 +141,12 @@ public class SystemUIFactory {
}
public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+ ScrimView scrimForBubble,
LockscreenWallpaper lockscreenWallpaper,
TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
- return new ScrimController(scrimBehind, scrimInFront, scrimStateListener,
+ return new ScrimController(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
deleted file mode 100644
index 74ad0faca6d3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
+++ /dev/null
@@ -1,94 +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 android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-// XXX: Mostly opied from launcher code / can we share?
-/**
- * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
- */
-public class BadgeRenderer {
-
- private static final String TAG = "BadgeRenderer";
-
- /** The badge sizes are defined as percentages of the app icon size. */
- private static final float SIZE_PERCENTAGE = 0.38f;
-
- /** Extra scale down of the dot. */
- private static final float DOT_SCALE = 0.6f;
-
- private final float mDotCenterOffset;
- private final float mCircleRadius;
- private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
- public BadgeRenderer(Context context) {
- mDotCenterOffset = getDotCenterOffset(context);
- mCircleRadius = getDotRadius(mDotCenterOffset);
- }
-
- /** Space between the center of the dot and the top or left of the bubble stack. */
- static float getDotCenterOffset(Context context) {
- final int iconSizePx =
- context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
- return SIZE_PERCENTAGE * iconSizePx;
- }
-
- static float getDotRadius(float dotCenterOffset) {
- int size = (int) (DOT_SCALE * dotCenterOffset);
- return size / 2f;
- }
-
- /**
- * Draw a circle in the top right corner of the given bounds.
- *
- * @param color The color (based on the icon) to use for the badge.
- * @param iconBounds The bounds of the icon being badged.
- * @param badgeScale The progress of the animation, from 0 to 1.
- * @param spaceForOffset How much space to offset the badge up and to the left or right.
- * @param onLeft Whether the badge should be draw on left or right side.
- */
- public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
- Point spaceForOffset, boolean onLeft) {
- if (iconBounds == null) {
- Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
- return;
- }
- canvas.save();
- // We draw the badge relative to its center.
- int x = onLeft ? iconBounds.left : iconBounds.right;
- float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
- float badgeCenterX = x + offset;
- float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
-
- canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
-
- canvas.scale(badgeScale, badgeScale);
- mCirclePaint.setColor(color);
- canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
- canvas.restore();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 783780f8819c..c0053d194ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -18,12 +18,13 @@ package com.android.systemui.bubbles;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Point;
+import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.DotRenderer;
import com.android.systemui.R;
/**
@@ -31,16 +32,19 @@ import com.android.systemui.R;
*/
public class BadgedImageView extends ImageView {
- private BadgeRenderer mDotRenderer;
- private int mIconSize;
private Rect mTempBounds = new Rect();
- private Point mTempPoint = new Point();
+ private DotRenderer mDotRenderer;
+ private DotRenderer.DrawParams mDrawParams;
+ private int mIconBitmapSize;
+ private int mDotColor;
private float mDotScale = 0f;
- private int mUpdateDotColor;
- private boolean mShowUpdateDot;
+ private boolean mShowDot;
private boolean mOnLeft;
+ /** Same as value in Launcher3 IconShape */
+ static final int DEFAULT_PATH_SIZE = 100;
+
public BadgedImageView(Context context) {
this(context, null);
}
@@ -56,69 +60,100 @@ public class BadgedImageView extends ImageView {
public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
- mDotRenderer = new BadgeRenderer(getContext());
+ mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+ mDrawParams = new DotRenderer.DrawParams();
TypedArray ta = context.obtainStyledAttributes(
- new int[] {android.R.attr.colorBackgroundFloating});
+ new int[]{android.R.attr.colorBackgroundFloating});
ta.recycle();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
- if (mShowUpdateDot) {
- getDrawingRect(mTempBounds);
- mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
- mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
- mOnLeft);
+ if (!mShowDot) {
+ return;
+ }
+ getDrawingRect(mTempBounds);
+
+ mDrawParams.color = mDotColor;
+ mDrawParams.iconBounds = mTempBounds;
+ mDrawParams.leftAlign = mOnLeft;
+ mDrawParams.scale = mDotScale;
+
+ if (mDotRenderer == null) {
+ Path circlePath = new Path();
+ float radius = DEFAULT_PATH_SIZE * 0.5f;
+ circlePath.addCircle(radius /* x */, radius /* y */, radius, Path.Direction.CW);
+ mDotRenderer = new DotRenderer(mIconBitmapSize, circlePath, DEFAULT_PATH_SIZE);
}
+ mDotRenderer.draw(canvas, mDrawParams);
}
/**
* Set whether the dot should appear on left or right side of the view.
*/
- public void setDotPosition(boolean onLeft) {
+ void setDotOnLeft(boolean onLeft) {
mOnLeft = onLeft;
invalidate();
}
- public boolean getDotPosition() {
+ boolean getDotOnLeft() {
return mOnLeft;
}
/**
* Set whether the dot should show or not.
*/
- public void setShowDot(boolean showBadge) {
- mShowUpdateDot = showBadge;
+ void setShowDot(boolean showDot) {
+ mShowDot = showDot;
invalidate();
}
/**
* @return whether the dot is being displayed.
*/
- public boolean isShowingDot() {
- return mShowUpdateDot;
+ boolean isShowingDot() {
+ return mShowDot;
}
/**
* The colour to use for the dot.
*/
public void setDotColor(int color) {
- mUpdateDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+ mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+ invalidate();
+ }
+
+ /**
+ * @param iconPath The new icon path to use when calculating dot position.
+ */
+ public void drawDot(Path iconPath) {
+ mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
invalidate();
}
/**
* How big the dot should be, fraction from 0 to 1.
*/
- public void setDotScale(float fraction) {
+ void setDotScale(float fraction) {
mDotScale = fraction;
invalidate();
}
- public float getDotScale() {
- return mDotScale;
+ /**
+ * Return dot position relative to bubble view container bounds.
+ */
+ float[] getDotCenter() {
+ float[] dotPosition;
+ if (mOnLeft) {
+ dotPosition = mDotRenderer.getLeftDotPosition();
+ } else {
+ dotPosition = mDotRenderer.getRightDotPosition();
+ }
+ getDrawingRect(mTempBounds);
+ float dotCenterX = mTempBounds.width() * dotPosition[0];
+ float dotCenterY = mTempBounds.height() * dotPosition[1];
+ return new float[]{dotCenterX, dotCenterY};
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 5c6c39722900..c3cee35d37fb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -20,38 +20,67 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
import java.util.Objects;
/**
* Encapsulates the data and UI elements of a bubble.
*/
class Bubble {
-
- private static final boolean DEBUG = false;
private static final String TAG = "Bubble";
+ private NotificationEntry mEntry;
private final String mKey;
private final String mGroupId;
private String mAppName;
- private final BubbleExpandedView.OnBubbleBlockedListener mListener;
+ private Drawable mUserBadgedAppIcon;
private boolean mInflated;
- public NotificationEntry entry;
- BubbleView iconView;
- BubbleExpandedView expandedView;
+ private BubbleView mIconView;
+ private BubbleExpandedView mExpandedView;
+
private long mLastUpdated;
private long mLastAccessed;
- private PackageManager mPm;
+ private boolean mIsRemoved;
+
+ /**
+ * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+ *
+ * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+ * expanded</p>
+ */
+ private boolean mShowInShadeWhenBubble = true;
+
+ /**
+ * Whether the bubble should show a dot for the notification indicating updated content.
+ */
+ private boolean mShowBubbleUpdateDot = true;
+
+ /** Whether flyout text should be suppressed, regardless of any other flags or state. */
+ private boolean mSuppressFlyout;
public static String groupId(NotificationEntry entry) {
UserHandle user = entry.notification.getUser();
@@ -61,31 +90,27 @@ class Bubble {
/** Used in tests when no UI is required. */
@VisibleForTesting(visibility = PRIVATE)
Bubble(Context context, NotificationEntry e) {
- this (context, e, null);
- }
-
- Bubble(Context context, NotificationEntry e,
- BubbleExpandedView.OnBubbleBlockedListener listener) {
- entry = e;
+ mEntry = e;
mKey = e.key;
mLastUpdated = e.notification.getPostTime();
mGroupId = groupId(e);
- mListener = listener;
- mPm = context.getPackageManager();
+ PackageManager pm = context.getPackageManager();
ApplicationInfo info;
try {
- info = mPm.getApplicationInfo(
- entry.notification.getPackageName(),
+ info = pm.getApplicationInfo(
+ mEntry.notification.getPackageName(),
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
- mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ mAppName = String.valueOf(pm.getApplicationLabel(info));
}
+ Drawable appIcon = pm.getApplicationIcon(mEntry.notification.getPackageName());
+ mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.notification.getUser());
} catch (PackageManager.NameNotFoundException unused) {
- mAppName = entry.notification.getPackageName();
+ mAppName = mEntry.notification.getPackageName();
}
}
@@ -93,12 +118,16 @@ class Bubble {
return mKey;
}
+ public NotificationEntry getEntry() {
+ return mEntry;
+ }
+
public String getGroupId() {
return mGroupId;
}
public String getPackageName() {
- return entry.notification.getPackageName();
+ return mEntry.notification.getPackageName();
}
public String getAppName() {
@@ -109,9 +138,23 @@ class Bubble {
return mInflated;
}
- public void updateDotVisibility() {
- if (iconView != null) {
- iconView.updateDotVisibility(true /* animate */);
+ void updateDotVisibility() {
+ if (mIconView != null) {
+ mIconView.updateDotVisibility(true /* animate */);
+ }
+ }
+
+ BubbleView getIconView() {
+ return mIconView;
+ }
+
+ BubbleExpandedView getExpandedView() {
+ return mExpandedView;
+ }
+
+ void cleanupExpandedState() {
+ if (mExpandedView != null) {
+ mExpandedView.cleanUpExpandedState();
}
}
@@ -119,14 +162,14 @@ class Bubble {
if (mInflated) {
return;
}
- iconView = (BubbleView) inflater.inflate(
+ mIconView = (BubbleView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
- iconView.setNotif(entry);
+ mIconView.setBubble(this);
+ mIconView.setAppIcon(mUserBadgedAppIcon);
- expandedView = (BubbleExpandedView) inflater.inflate(
+ mExpandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- expandedView.setEntry(entry, stackView, mAppName);
- expandedView.setOnBlockedListener(mListener);
+ mExpandedView.setBubble(this, stackView, mAppName);
mInflated = true;
}
@@ -140,46 +183,38 @@ class Bubble {
* and setting {@code false} actually means rendering the expanded view in transparent.
*/
void setContentVisibility(boolean visibility) {
- if (expandedView != null) {
- expandedView.setContentVisibility(visibility);
+ if (mExpandedView != null) {
+ mExpandedView.setContentVisibility(visibility);
}
}
- void setDismissed() {
- entry.setBubbleDismissed(true);
- // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker
- if (expandedView != null) {
- expandedView.cleanUpExpandedState();
- }
- }
-
- void setEntry(NotificationEntry entry) {
- this.entry = entry;
+ void updateEntry(NotificationEntry entry) {
+ mEntry = entry;
mLastUpdated = entry.notification.getPostTime();
if (mInflated) {
- iconView.update(entry);
- expandedView.update(entry);
+ mIconView.update(this);
+ mExpandedView.update(this);
}
}
/**
* @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
*/
- public long getLastActivity() {
+ long getLastActivity() {
return Math.max(mLastUpdated, mLastAccessed);
}
/**
* @return the timestamp in milliseconds of the most recent notification entry for this bubble
*/
- public long getLastUpdateTime() {
+ long getLastUpdateTime() {
return mLastUpdated;
}
/**
* @return the timestamp in milliseconds when this bubble was last displayed in expanded state
*/
- public long getLastAccessTime() {
+ long getLastAccessTime() {
return mLastAccessed;
}
@@ -187,7 +222,7 @@ class Bubble {
* @return the display id of the virtual display on which bubble contents is drawn.
*/
int getDisplayId() {
- return expandedView != null ? expandedView.getVirtualDisplayId() : INVALID_DISPLAY;
+ return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
}
/**
@@ -195,14 +230,204 @@ class Bubble {
*/
void markAsAccessedAt(long lastAccessedMillis) {
mLastAccessed = lastAccessedMillis;
- entry.setShowInShadeWhenBubble(false);
+ setShowInShadeWhenBubble(false);
+ setShowBubbleDot(false);
+ }
+
+ /**
+ * Whether this notification should be shown in the shade when it is also displayed as a
+ * bubble.
+ */
+ boolean showInShadeWhenBubble() {
+ return !mEntry.isRowDismissed() && !shouldSuppressNotification()
+ && (!mEntry.isClearable() || mShowInShadeWhenBubble);
+ }
+
+ /**
+ * Sets whether this notification should be shown in the shade when it is also displayed as a
+ * bubble.
+ */
+ void setShowInShadeWhenBubble(boolean showInShade) {
+ mShowInShadeWhenBubble = showInShade;
+ }
+
+ /**
+ * Sets whether the bubble for this notification should show a dot indicating updated content.
+ */
+ void setShowBubbleDot(boolean showDot) {
+ mShowBubbleUpdateDot = showDot;
}
/**
- * @return whether bubble is from a notification associated with a foreground service.
+ * Whether the bubble for this notification should show a dot indicating updated content.
*/
- public boolean isOngoing() {
- return entry.isForegroundService();
+ boolean showBubbleDot() {
+ return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot();
+ }
+
+ /**
+ * Whether the flyout for the bubble should be shown.
+ */
+ boolean showFlyoutForBubble() {
+ return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
+ && !mEntry.shouldSuppressNotificationList();
+ }
+
+ /**
+ * Set whether the flyout text for the bubble should be shown when an update is received.
+ *
+ * @param suppressFlyout whether the flyout text is shown
+ */
+ void setSuppressFlyout(boolean suppressFlyout) {
+ mSuppressFlyout = suppressFlyout;
+ }
+
+ /**
+ * Returns whether the notification for this bubble is a foreground service. It shows that this
+ * is an ongoing bubble.
+ */
+ boolean isOngoing() {
+ int flags = mEntry.notification.getNotification().flags;
+ return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
+ }
+
+ float getDesiredHeight(Context context) {
+ Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+ boolean useRes = data.getDesiredHeightResId() != 0;
+ if (useRes) {
+ return getDimenForPackageUser(context, data.getDesiredHeightResId(),
+ mEntry.notification.getPackageName(),
+ mEntry.notification.getUser().getIdentifier());
+ } else {
+ return data.getDesiredHeight()
+ * context.getResources().getDisplayMetrics().density;
+ }
+ }
+
+ String getDesiredHeightString() {
+ Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+ boolean useRes = data.getDesiredHeightResId() != 0;
+ if (useRes) {
+ return String.valueOf(data.getDesiredHeightResId());
+ } else {
+ return String.valueOf(data.getDesiredHeight());
+ }
+ }
+
+ @Nullable
+ PendingIntent getBubbleIntent(Context context) {
+ Notification notif = mEntry.notification.getNotification();
+ Notification.BubbleMetadata data = notif.getBubbleMetadata();
+ if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
+ return data.getIntent();
+ }
+ return null;
+ }
+
+ Intent getSettingsIntent() {
+ final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
+ intent.putExtra(Settings.EXTRA_APP_UID, mEntry.notification.getUid());
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return intent;
+ }
+
+ /**
+ * Returns our best guess for the most relevant text summary of the latest update to this
+ * notification, based on its type. Returns null if there should not be an update message.
+ */
+ CharSequence getUpdateMessage(Context context) {
+ final Notification underlyingNotif = mEntry.notification.getNotification();
+ final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+ try {
+ if (Notification.BigTextStyle.class.equals(style)) {
+ // Return the big text, it is big so probably important. If it's not there use the
+ // normal text.
+ CharSequence bigText =
+ underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+ return !TextUtils.isEmpty(bigText)
+ ? bigText
+ : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+ } else if (Notification.MessagingStyle.class.equals(style)) {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) underlyingNotif.extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+
+ if (latestMessage != null) {
+ final CharSequence personName = latestMessage.getSenderPerson() != null
+ ? latestMessage.getSenderPerson().getName()
+ : null;
+
+ // Prepend the sender name if available since group chats also use messaging
+ // style.
+ if (!TextUtils.isEmpty(personName)) {
+ return context.getResources().getString(
+ R.string.notification_summary_message_format,
+ personName,
+ latestMessage.getText());
+ } else {
+ return latestMessage.getText();
+ }
+ }
+ } else if (Notification.InboxStyle.class.equals(style)) {
+ CharSequence[] lines =
+ underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+ // Return the last line since it should be the most recent.
+ if (lines != null && lines.length > 0) {
+ return lines[lines.length - 1];
+ }
+ } else if (Notification.MediaStyle.class.equals(style)) {
+ // Return nothing, media updates aren't typically useful as a text update.
+ return null;
+ } else {
+ // Default to text extra.
+ return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+ }
+ } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+ // No use crashing, we'll just return null and the caller will assume there's no update
+ // message.
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
+ PackageManager pm = context.getPackageManager();
+ Resources r;
+ if (pkg != null) {
+ try {
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ }
+ r = pm.getResourcesForApplicationAsUser(pkg, userId);
+ return r.getDimensionPixelSize(resId);
+ } catch (PackageManager.NameNotFoundException ex) {
+ // Uninstalled, don't care
+ } catch (Resources.NotFoundException e) {
+ // Invalid res id, return 0 and user our default
+ Log.e(TAG, "Couldn't find desired height res id", e);
+ }
+ }
+ return 0;
+ }
+
+ private boolean shouldSuppressNotification() {
+ return mEntry.getBubbleMetadata() != null
+ && mEntry.getBubbleMetadata().isNotificationSuppressed();
+ }
+
+ boolean shouldAutoExpand() {
+ Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
+ return metadata != null && metadata.getAutoExpandBubble();
}
@Override
@@ -210,6 +435,20 @@ class Bubble {
return "Bubble{" + mKey + '}';
}
+ /**
+ * Description of current bubble state.
+ */
+ public void dump(
+ @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.print("key: "); pw.println(mKey);
+ pw.print(" showInShade: "); pw.println(showInShadeWhenBubble());
+ pw.print(" showDot: "); pw.println(showBubbleDot());
+ pw.print(" showFlyout: "); pw.println(showFlyoutForBubble());
+ pw.print(" desiredHeight: "); pw.println(getDesiredHeightString());
+ pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
+ pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a23c99ef01fe..94d9ede5c8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,21 +16,24 @@
package com.android.systemui.bubbles;
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
@@ -39,9 +42,8 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.app.ActivityManager;
+import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -53,10 +55,11 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseSetArray;
import android.view.Display;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -75,18 +78,23 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@@ -101,12 +109,12 @@ import javax.inject.Singleton;
@Singleton
public class BubbleController implements ConfigurationController.ConfigurationListener {
- private static final String TAG = "BubbleController";
- private static final boolean DEBUG = false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@Retention(SOURCE)
@IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
- DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
+ DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
+ DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
@interface DismissReason {}
@@ -117,24 +125,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
static final int DISMISS_NOTIF_CANCEL = 5;
static final int DISMISS_ACCESSIBILITY_ACTION = 6;
static final int DISMISS_NO_LONGER_BUBBLE = 7;
+ static final int DISMISS_USER_CHANGED = 8;
+ static final int DISMISS_GROUP_CANCELLED = 9;
+ static final int DISMISS_INVALID_INTENT = 10;
public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
- // Enables some subset of notifs to automatically become bubbles
- public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
-
/** Flag to enable or disable the entire feature */
private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
- /** Auto bubble flags set whether different notif types should be presented as a bubble */
- private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
- private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
- private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
-
- /** Use an activityView for an auto-bubbled notifs if it has an appropriate content intent */
- private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
-
- private static final String BUBBLE_STIFFNESS = "experiment_bubble_stiffness";
- private static final String BUBBLE_BOUNCINESS = "experiment_bubble_bounciness";
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
@@ -142,10 +140,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private BubbleStateChangeListener mStateChangeListener;
private BubbleExpandListener mExpandListener;
@Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+ private final NotificationGroupManager mNotificationGroupManager;
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
+ // Tracks the id of the current (foreground) user.
+ private int mCurrentUserId;
+ // Saves notification keys of active bubbles when users are switched.
+ private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+
// Bubbles get added to the status bar view
private final StatusBarWindowController mStatusBarWindowController;
private final ZenModeController mZenModeController;
@@ -157,6 +161,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
+ // Listens to user switch so bubbles can be saved and restored.
+ private final NotificationLockscreenUserManager mNotifUserManager;
+
/** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -211,28 +218,38 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
BubbleData data, ConfigurationController configurationController,
NotificationInterruptionStateProvider interruptionStateProvider,
- ZenModeController zenModeController) {
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManager groupManager) {
this(context, statusBarWindowController, data, null /* synchronizer */,
- configurationController, interruptionStateProvider, zenModeController);
+ configurationController, interruptionStateProvider, zenModeController,
+ notifUserManager, groupManager);
}
public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
ConfigurationController configurationController,
NotificationInterruptionStateProvider interruptionStateProvider,
- ZenModeController zenModeController) {
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManager groupManager) {
mContext = context;
mNotificationInterruptionStateProvider = interruptionStateProvider;
+ mNotifUserManager = notifUserManager;
mZenModeController = zenModeController;
mZenModeController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
- updateStackViewForZenConfig();
+ if (mStackView != null) {
+ mStackView.updateDots();
+ }
}
@Override
public void onConfigChanged(ZenModeConfig config) {
- updateStackViewForZenConfig();
+ if (mStackView != null) {
+ mStackView.updateDots();
+ }
}
});
@@ -244,6 +261,24 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
+ mNotificationGroupManager = groupManager;
+ mNotificationGroupManager.addOnGroupChangeListener(
+ new NotificationGroupManager.OnGroupChangeListener() {
+ @Override
+ public void onGroupSuppressionChanged(
+ NotificationGroupManager.NotificationGroup group,
+ boolean suppressed) {
+ // More notifications could be added causing summary to no longer
+ // be suppressed -- in this case need to remove the key.
+ final String groupKey = group.summary != null
+ ? group.summary.notification.getGroupKey()
+ : null;
+ if (!suppressed && groupKey != null
+ && mBubbleData.isSummarySuppressed(groupKey)) {
+ mBubbleData.removeSuppressedSummary(groupKey);
+ }
+ }
+ });
mStatusBarWindowController = statusBarWindowController;
mStatusBarStateListener = new StatusBarStateListener();
@@ -261,6 +296,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+ mSavedBubbleKeysPerUser = new SparseSetArray<>();
+ mCurrentUserId = mNotifUserManager.getCurrentUserId();
+ mNotifUserManager.addUserChangedListener(
+ newUserId -> {
+ saveBubbles(mCurrentUserId);
+ mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+ restoreBubbles(newUserId);
+ mCurrentUserId = newUserId;
+ });
}
/**
@@ -271,19 +316,55 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mStackView == null) {
mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
- // TODO(b/130237686): 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,
+ int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
+ int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim.
+ sbv.addView(mStackView, stackIndex,
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
+ }
+ }
- updateStackViewForZenConfig();
+ /**
+ * Records the notification key for any active bubbles. These are used to restore active
+ * bubbles when the user returns to the foreground.
+ *
+ * @param userId the id of the user
+ */
+ private void saveBubbles(@UserIdInt int userId) {
+ // First clear any existing keys that might be stored.
+ mSavedBubbleKeysPerUser.remove(userId);
+ // Add in all active bubbles for the current user.
+ for (Bubble bubble: mBubbleData.getBubbles()) {
+ mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
}
}
+ /**
+ * Promotes existing notifications to Bubbles if they were previously bubbles.
+ *
+ * @param userId the id of the user
+ */
+ private void restoreBubbles(@UserIdInt int userId) {
+ NotificationData notificationData =
+ mNotificationEntryManager.getNotificationData();
+ ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
+ if (savedBubbleKeys == null) {
+ // There were no bubbles saved for this used.
+ return;
+ }
+ for (NotificationEntry e : notificationData.getNotificationsForCurrentUser()) {
+ if (savedBubbleKeys.contains(e.key)
+ && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
+ && canLaunchInActivityView(mContext, e)) {
+ updateBubble(e, /* suppressFlyout= */ true);
+ }
+ }
+ // Finally, remove the entries for this user now that bubbles are restored.
+ mSavedBubbleKeysPerUser.remove(mCurrentUserId);
+ }
+
@Override
public void onUiModeChanged() {
if (mStackView != null) {
@@ -301,8 +382,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onConfigChanged(Configuration newConfig) {
if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
- mStackView.onOrientationChanged();
mOrientation = newConfig.orientation;
+ mStackView.onOrientationChanged(newConfig.orientation);
}
}
@@ -360,6 +441,25 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBubbleData.setExpanded(false /* expanded */);
}
+ /**
+ * True if either:
+ * (1) There is a bubble associated with the provided key and if its notification is hidden
+ * from the shade.
+ * (2) There is a group summary associated with the provided key that is hidden from the shade
+ * because it has been dismissed but still has child bubbles active.
+ *
+ * False otherwise.
+ */
+ public boolean isBubbleNotificationSuppressedFromShade(String key) {
+ boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
+ && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble();
+ NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+ String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+ boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
+ boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
+ return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
+ }
+
void selectBubble(Bubble bubble) {
mBubbleData.setSelectedBubble(bubble);
}
@@ -406,11 +506,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* @param notif the notification associated with this bubble.
*/
void updateBubble(NotificationEntry notif) {
+ updateBubble(notif, /* supressFlyout */ false);
+ }
+
+ void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
// If this is an interruptive notif, mark that it's interrupted
if (notif.importance >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
}
- mBubbleData.notificationEntryUpdated(notif);
+ mBubbleData.notificationEntryUpdated(notif, suppressFlyout);
}
/**
@@ -423,7 +527,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// TEMP: refactor to change this to pass entry
Bubble bubble = mBubbleData.getBubbleWithKey(key);
if (bubble != null) {
- mBubbleData.notificationEntryRemoved(bubble.entry, reason);
+ mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
}
}
@@ -432,33 +536,50 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
new NotificationRemoveInterceptor() {
@Override
public boolean onNotificationRemoveRequested(String key, int reason) {
- if (!mBubbleData.hasBubbleWithKey(key)) {
+ NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+ String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+ ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+ boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
+ boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
+ && mBubbleData.getSummaryKey(groupKey).equals(key));
+ boolean isSummary = entry != null
+ && entry.notification.getNotification().isGroupSummary();
+ boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
+ && bubbleChildren != null && !bubbleChildren.isEmpty();
+
+ if (!inBubbleData && !isSummaryOfBubbles) {
return false;
}
- NotificationEntry entry = mBubbleData.getBubbleWithKey(key).entry;
final boolean isClearAll = reason == REASON_CANCEL_ALL;
- final boolean isUserDimiss = reason == REASON_CANCEL;
+ final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
final boolean isAppCancel = reason == REASON_APP_CANCEL
|| reason == REASON_APP_CANCEL_ALL;
+ final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
// Need to check for !appCancel here because the notification may have
// previously been dismissed & entry.isRowDismissed would still be true
boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel)
- || isClearAll || isUserDimiss;
+ || isClearAll || isUserDimiss || isSummaryCancel;
+
+ if (isSummaryOfBubbles) {
+ return handleSummaryRemovalInterception(entry, userRemovedNotif);
+ }
// The bubble notification sticks around in the data as long as the bubble is
// not dismissed and the app hasn't cancelled the notification.
- boolean bubbleExtended = entry.isBubble() && !entry.isBubbleDismissed()
- && userRemovedNotif;
+ Bubble bubble = mBubbleData.getBubbleWithKey(key);
+ boolean bubbleExtended = entry.isBubble() && userRemovedNotif;
if (bubbleExtended) {
- entry.setShowInShadeWhenBubble(false);
+ bubble.setShowInShadeWhenBubble(false);
+ bubble.setShowBubbleDot(false);
if (mStackView != null) {
mStackView.updateDotVisibility(entry.key);
}
mNotificationEntryManager.updateNotifications();
return true;
- } else if (!userRemovedNotif && !entry.isBubbleDismissed()) {
+ } else if (!userRemovedNotif) {
// This wasn't a user removal so we should remove the bubble as well
mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
return false;
@@ -467,21 +588,56 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
};
- @SuppressWarnings("FieldCanBeLocal")
- private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- if (!areBubblesEnabled(mContext)) {
- return;
+ private boolean handleSummaryRemovalInterception(NotificationEntry summary,
+ boolean userRemovedNotif) {
+ String groupKey = summary.notification.getGroupKey();
+ ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+ if (userRemovedNotif) {
+ // If it's a user dismiss we mark the children to be hidden from the shade.
+ for (int i = 0; i < bubbleChildren.size(); i++) {
+ Bubble bubbleChild = bubbleChildren.get(i);
+ // As far as group manager is concerned, once a child is no longer shown
+ // in the shade, it is essentially removed.
+ mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
+ bubbleChild.setShowInShadeWhenBubble(false);
+ bubbleChild.setShowBubbleDot(false);
+ if (mStackView != null) {
+ mStackView.updateDotVisibility(bubbleChild.getKey());
+ }
}
- if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
- && canLaunchInActivityView(mContext, entry)) {
- updateShowInShadeForSuppressNotification(entry);
+ // And since all children are removed, remove the summary.
+ mNotificationGroupManager.onEntryRemoved(summary);
+
+ // If the summary was auto-generated we don't need to keep that notification around
+ // because apps can't cancel it; so we only intercept & suppress real summaries.
+ boolean isAutogroupSummary = (summary.notification.getNotification().flags
+ & FLAG_AUTOGROUP_SUMMARY) != 0;
+ if (!isAutogroupSummary) {
+ mBubbleData.addSummaryToSuppress(summary.notification.getGroupKey(),
+ summary.key);
+ // Tell shade to update for the suppression
+ mNotificationEntryManager.updateNotifications();
}
+ return !isAutogroupSummary;
+ } else {
+ // If it's not a user dismiss it's a cancel.
+ mBubbleData.removeSuppressedSummary(groupKey);
+
+ // Remove any associated bubble children.
+ for (int i = 0; i < bubbleChildren.size(); i++) {
+ Bubble bubbleChild = bubbleChildren.get(i);
+ mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
+ DISMISS_GROUP_CANCELLED);
+ }
+ return false;
}
+ }
+ @SuppressWarnings("FieldCanBeLocal")
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
@Override
- public void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
if (!areBubblesEnabled(mContext)) {
return;
}
@@ -502,8 +658,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// It was previously a bubble but no longer a bubble -- lets remove it
removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE);
} else if (shouldBubble) {
- updateShowInShadeForSuppressNotification(entry);
- entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+ Bubble b = mBubbleData.getBubbleWithKey(entry.key);
updateBubble(entry);
}
}
@@ -540,27 +695,60 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
// Do removals, if any.
- for (Pair<Bubble, Integer> removed : update.removedBubbles) {
+ ArrayList<Pair<Bubble, Integer>> removedBubbles =
+ new ArrayList<>(update.removedBubbles);
+ for (Pair<Bubble, Integer> removed : removedBubbles) {
final Bubble bubble = removed.first;
@DismissReason final int reason = removed.second;
mStackView.removeBubble(bubble);
- if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
- && !bubble.entry.showInShadeWhenBubble()) {
- // The bubble is gone & the notification is gone, time to actually remove it
- mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
- UNDEFINED_DISMISS_REASON);
- } else {
- // Update the flag for SysUI
- bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
-
- // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
- // get right result back
- try {
- mBarService.onNotificationBubbleChanged(bubble.getKey(),
- false /* isBubble */);
- } catch (RemoteException e) {
- // Bad things have happened
+ // If the bubble is removed for user switching, leave the notification in place.
+ if (reason != DISMISS_USER_CHANGED) {
+ if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
+ && !bubble.showInShadeWhenBubble()) {
+ // The bubble is gone & the notification is gone, time to actually remove it
+ mNotificationEntryManager.performRemoveNotification(
+ bubble.getEntry().notification, UNDEFINED_DISMISS_REASON);
+ } else {
+ // Update the flag for SysUI
+ bubble.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE;
+
+ // Make sure NoMan knows it's not a bubble anymore so anyone querying it
+ // will get right result back
+ try {
+ mBarService.onNotificationBubbleChanged(bubble.getKey(),
+ false /* isBubble */);
+ } catch (RemoteException e) {
+ // Bad things have happened
+ }
+ }
+
+ // Check if removed bubble has an associated suppressed group summary that needs
+ // to be removed now.
+ final String groupKey = bubble.getEntry().notification.getGroupKey();
+ if (mBubbleData.isSummarySuppressed(groupKey)
+ && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+ // Time to actually remove the summary.
+ String notifKey = mBubbleData.getSummaryKey(groupKey);
+ mBubbleData.removeSuppressedSummary(groupKey);
+ NotificationEntry entry =
+ mNotificationEntryManager.getNotificationData().get(notifKey);
+ mNotificationEntryManager.performRemoveNotification(
+ entry.notification, UNDEFINED_DISMISS_REASON);
+ }
+
+ // Check if summary should be removed from NoManGroup
+ NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
+ bubble.getEntry().notification);
+ if (summary != null) {
+ ArrayList<NotificationEntry> summaryChildren =
+ mNotificationGroupManager.getLogicalChildren(summary.notification);
+ boolean isSummaryThisNotif = summary.key.equals(bubble.getEntry().key);
+ if (!isSummaryThisNotif
+ && (summaryChildren == null || summaryChildren.isEmpty())) {
+ mNotificationEntryManager.performRemoveNotification(
+ summary.notification, UNDEFINED_DISMISS_REASON);
+ }
}
}
}
@@ -575,6 +763,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (update.selectionChanged) {
mStackView.setSelectedBubble(update.selectedBubble);
+ if (update.selectedBubble != null) {
+ mNotificationGroupManager.updateSuppression(
+ update.selectedBubble.getEntry());
+ }
}
// Expanding? Apply this last.
@@ -585,7 +777,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mNotificationEntryManager.updateNotifications();
updateStack();
- if (DEBUG) {
+ if (DEBUG_BUBBLE_CONTROLLER) {
Log.d(TAG, "[BubbleData]");
Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
mBubbleData.getSelectedBubble()));
@@ -600,34 +792,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
};
/**
- * Updates the stack view's suppression flags from the latest config from the zen (do not
- * disturb) controller.
- */
- private void updateStackViewForZenConfig() {
- final ZenModeConfig zenModeConfig = mZenModeController.getConfig();
-
- if (zenModeConfig == null || mStackView == null) {
- return;
- }
-
- final int suppressedEffects = zenModeConfig.suppressedVisualEffects;
- final boolean hideNotificationDotsSelected =
- (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0;
- final boolean dontPopNotifsOnScreenSelected =
- (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0;
- final boolean hideFromPullDownShadeSelected =
- (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
-
- final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
-
- mStackView.setSuppressNewDot(
- dndEnabled && hideNotificationDotsSelected);
- mStackView.setSuppressFlyout(
- dndEnabled && (dontPopNotifsOnScreenSelected
- || hideFromPullDownShadeSelected));
- }
-
- /**
* Lets any listeners know if bubble state has changed.
* Updates the visibility of the bubbles based on current state.
* Does not un-bubble, just hides or un-hides. Notifies any
@@ -697,46 +861,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * Whether the notification should automatically bubble or not. Gated by secure settings flags.
+ * Description of current bubble state.
*/
- @VisibleForTesting
- protected boolean shouldAutoBubbleForFlags(Context context, NotificationEntry entry) {
- if (entry.isBubbleDismissed()) {
- return false;
- }
- StatusBarNotification n = entry.notification;
-
- boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
- boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
- boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-
- boolean hasRemoteInput = false;
- if (n.getNotification().actions != null) {
- for (Notification.Action action : n.getNotification().actions) {
- if (action.getRemoteInputs() != null) {
- hasRemoteInput = true;
- break;
- }
- }
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("BubbleController state:");
+ mBubbleData.dump(fd, pw, args);
+ pw.println();
+ if (mStackView != null) {
+ mStackView.dump(fd, pw, args);
}
- boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
- && n.isOngoing();
- boolean isMusic = n.getNotification().hasMediaSession();
- boolean isImportantOngoing = isMusic || isCall;
-
- Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
- boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
- boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
- return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
- || (isImportantOngoing && autoBubbleOngoing)
- || autoBubbleAll;
- }
-
- private void updateShowInShadeForSuppressNotification(NotificationEntry entry) {
- boolean suppressNotification = entry.getBubbleMetadata() != null
- && entry.getBubbleMetadata().isNotificationSuppressed()
- && isForegroundApp(mContext, entry.notification.getPackageName());
- entry.setShowInShadeWhenBubble(!suppressNotification);
+ pw.println();
}
static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
@@ -757,18 +891,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * Return true if the applications with the package name is running in foreground.
- *
- * @param context application context.
- * @param pkgName application package name.
- */
- public static boolean isForegroundApp(Context context, String pkgName) {
- ActivityManager am = context.getSystemService(ActivityManager.class);
- List<RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */);
- return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
- }
-
- /**
* This task stack listener is responsible for responding to tasks moved to the front
* which are on the default (main) display. When this happens, expanded bubbles must be
* collapsed so the user may interact with the app which was just moved to the front.
@@ -782,7 +904,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
- mBubbleData.setExpanded(false);
+ if (!mStackView.isExpansionAnimating()) {
+ mBubbleData.setExpanded(false);
+ }
}
}
@@ -807,26 +931,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
expandedBubble.setContentVisibility(true);
}
}
- }
-
- private static boolean shouldAutoBubbleMessages(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
- }
-
- private static boolean shouldAutoBubbleOngoing(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
- }
-
- private static boolean shouldAutoBubbleAll(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
- }
- static boolean shouldUseContentIntent(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId) {
+ final Bubble expandedBubble = getExpandedBubble(mContext);
+ if (expandedBubble == null) {
+ return;
+ }
+ if (expandedBubble.getDisplayId() == displayId) {
+ mBubbleData.setExpanded(false);
+ expandedBubble.getExpandedView().notifyDisplayEmpty();
+ }
+ }
}
private static boolean areBubblesEnabled(Context context) {
@@ -834,20 +950,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
ENABLE_BUBBLES, 1) != 0;
}
- /** Default stiffness to use for bubble physics animations. */
- public static int getBubbleStiffness(Context context, int defaultStiffness) {
- return Settings.Secure.getInt(
- context.getContentResolver(), BUBBLE_STIFFNESS, defaultStiffness);
- }
-
- /** Default bounciness/damping ratio to use for bubble physics animations. */
- public static float getBubbleBounciness(Context context, float defaultBounciness) {
- return Settings.Secure.getInt(
- context.getContentResolver(),
- BUBBLE_BOUNCINESS,
- (int) (defaultBounciness * 100)) / 100f;
- }
-
/**
* Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
*
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 5575b35a12ae..81c8da8247ec 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -16,6 +16,9 @@
package com.android.systemui.bubbles;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static java.util.stream.Collectors.toList;
@@ -33,11 +36,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.bubbles.BubbleController.DismissReason;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -51,8 +55,7 @@ import javax.inject.Singleton;
@Singleton
public class BubbleData {
- private static final String TAG = "BubbleData";
- private static final boolean DEBUG = false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
private static final int MAX_BUBBLES = 5;
@@ -123,6 +126,19 @@ public class BubbleData {
@Nullable
private Listener mListener;
+ /**
+ * We track groups with summaries that aren't visibly displayed but still kept around because
+ * the bubble(s) associated with the summary still exist.
+ *
+ * The summary must be kept around so that developers can cancel it (and hence the bubbles
+ * associated with it). This list is used to check if the summary should be hidden from the
+ * shade.
+ *
+ * Key: group key of the NotificationEntry
+ * Value: key of the NotificationEntry
+ */
+ private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
+
@Inject
public BubbleData(Context context) {
mContext = context;
@@ -148,7 +164,7 @@ public class BubbleData {
}
public void setExpanded(boolean expanded) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setExpanded: " + expanded);
}
setExpandedInternal(expanded);
@@ -156,29 +172,30 @@ public class BubbleData {
}
public void setSelectedBubble(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubble: " + bubble);
}
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
}
- public void notificationEntryUpdated(NotificationEntry entry) {
- if (DEBUG) {
+ void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "notificationEntryUpdated: " + entry);
}
Bubble bubble = getBubbleWithKey(entry.key);
if (bubble == null) {
// Create a new bubble
- bubble = new Bubble(mContext, entry, this::onBubbleBlocked);
+ bubble = new Bubble(mContext, entry);
+ bubble.setSuppressFlyout(suppressFlyout);
doAdd(bubble);
trim();
} else {
// Updates an existing bubble
- bubble.setEntry(entry);
+ bubble.updateEntry(entry);
doUpdate(bubble);
}
- if (shouldAutoExpand(entry)) {
+ if (bubble.shouldAutoExpand()) {
setSelectedBubbleInternal(bubble);
if (!mExpanded) {
setExpandedInternal(true);
@@ -186,11 +203,14 @@ public class BubbleData {
} else if (mSelectedBubble == null) {
setSelectedBubbleInternal(bubble);
}
+ boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
+ bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected);
+ bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
dispatchPendingChanges();
}
public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
}
doRemove(entry.key, reason);
@@ -222,8 +242,59 @@ public class BubbleData {
dispatchPendingChanges();
}
+ /**
+ * Adds a group key indicating that the summary for this group should be suppressed.
+ *
+ * @param groupKey the group key of the group whose summary should be suppressed.
+ * @param notifKey the notification entry key of that summary.
+ */
+ void addSummaryToSuppress(String groupKey, String notifKey) {
+ mSuppressedGroupKeys.put(groupKey, notifKey);
+ }
+
+ /**
+ * Retrieves the notif entry key of the summary associated with the provided group key.
+ *
+ * @param groupKey the group to look up
+ * @return the key for the {@link NotificationEntry} that is the summary of this group.
+ */
+ String getSummaryKey(String groupKey) {
+ return mSuppressedGroupKeys.get(groupKey);
+ }
+
+ /**
+ * Removes a group key indicating that summary for this group should no longer be suppressed.
+ */
+ void removeSuppressedSummary(String groupKey) {
+ mSuppressedGroupKeys.remove(groupKey);
+ }
+
+ /**
+ * Whether the summary for the provided group key is suppressed.
+ */
+ boolean isSummarySuppressed(String groupKey) {
+ return mSuppressedGroupKeys.containsKey(groupKey);
+ }
+
+ /**
+ * Retrieves any bubbles that are part of the notification group represented by the provided
+ * group key.
+ */
+ ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
+ ArrayList<Bubble> bubbleChildren = new ArrayList<>();
+ if (groupKey == null) {
+ return bubbleChildren;
+ }
+ for (Bubble b : mBubbles) {
+ if (groupKey.equals(b.getEntry().notification.getGroupKey())) {
+ bubbleChildren.add(b);
+ }
+ }
+ return bubbleChildren;
+ }
+
private void doAdd(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doAdd: " + bubble);
}
int minInsertPoint = 0;
@@ -256,7 +327,7 @@ public class BubbleData {
}
private void doUpdate(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doUpdate: " + bubble);
}
mStateChange.updatedBubble = bubble;
@@ -301,12 +372,11 @@ public class BubbleData {
Bubble newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
- bubbleToRemove.setDismissed();
- maybeSendDeleteIntent(reason, bubbleToRemove.entry);
+ maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
}
public void dismissAll(@DismissReason int reason) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "dismissAll: reason=" + reason);
}
if (mBubbles.isEmpty()) {
@@ -316,8 +386,7 @@ public class BubbleData {
setSelectedBubbleInternal(null);
while (!mBubbles.isEmpty()) {
Bubble bubble = mBubbles.remove(0);
- bubble.setDismissed();
- maybeSendDeleteIntent(reason, bubble.entry);
+ maybeSendDeleteIntent(reason, bubble.getEntry());
mStateChange.bubbleRemoved(bubble, reason);
}
dispatchPendingChanges();
@@ -336,7 +405,7 @@ public class BubbleData {
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
if (Objects.equals(bubble, mSelectedBubble)) {
@@ -361,7 +430,7 @@ public class BubbleData {
* @param shouldExpand the new requested state
*/
private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
}
if (mExpanded == shouldExpand) {
@@ -396,7 +465,7 @@ public class BubbleData {
// bubble remains on top.
mBubbles.remove(mSelectedBubble);
mBubbles.add(0, mSelectedBubble);
- packGroup(0);
+ mStateChange.orderChanged |= packGroup(0);
}
}
}
@@ -466,7 +535,7 @@ public class BubbleData {
* @return true if the position of any bubbles has changed as a result
*/
private boolean packGroup(int position) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "packGroup: position=" + position);
}
Bubble groupStart = mBubbles.get(position);
@@ -495,7 +564,7 @@ public class BubbleData {
* @return true if the position of any bubbles changed as a result
*/
private boolean repackAll() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "repackAll()");
}
if (mBubbles.isEmpty()) {
@@ -550,28 +619,6 @@ public class BubbleData {
}
}
- private void onBubbleBlocked(NotificationEntry entry) {
- final String blockedGroupId = Bubble.groupId(entry);
- int selectedIndex = mBubbles.indexOf(mSelectedBubble);
- for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
- Bubble bubble = i.next();
- if (bubble.getGroupId().equals(blockedGroupId)) {
- mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
- i.remove();
- }
- }
- if (mBubbles.isEmpty()) {
- setExpandedInternal(false);
- setSelectedBubbleInternal(null);
- } else if (!mBubbles.contains(mSelectedBubble)) {
- // choose a new one
- int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
- Bubble newSelected = mBubbles.get(newIndex);
- setSelectedBubbleInternal(newSelected);
- }
- dispatchPendingChanges();
- }
-
private int indexForKey(String key) {
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubble = mBubbles.get(i);
@@ -610,9 +657,21 @@ public class BubbleData {
mListener = listener;
}
- boolean shouldAutoExpand(NotificationEntry entry) {
- Notification.BubbleMetadata metadata = entry.getBubbleMetadata();
- return metadata != null && metadata.getAutoExpandBubble()
- && BubbleController.isForegroundApp(mContext, entry.notification.getPackageName());
+ /**
+ * Description of current bubble data state.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("selected: "); pw.println(mSelectedBubble != null
+ ? mSelectedBubble.getKey()
+ : "null");
+ pw.print("expanded: "); pw.println(mExpanded);
+ pw.print("count: "); pw.println(mBubbles.size());
+ for (Bubble bubble : mBubbles) {
+ bubble.dump(fd, pw, args);
+ }
+ pw.print("summaryKeys: "); pw.println(mSuppressedGroupKeys.size());
+ for (String key : mSuppressedGroupKeys.keySet()) {
+ pw.println(" suppressing: " + key);
+ }
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
new file mode 100644
index 000000000000..b702d06ca7cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
+ * package.
+ */
+public class BubbleDebugConfig {
+
+ // All output logs in the Bubbles package use the {@link #TAG_BUBBLES} string for tagging their
+ // log output. This makes it easy to identify the origin of the log message when sifting
+ // through a large amount of log output from multiple sources. However, it also makes trying
+ // to figure-out the origin of a log message while debugging the Bubbles a little painful. By
+ // setting this constant to true, log messages from the Bubbles package will be tagged with
+ // their class names instead fot the generic tag.
+ static final boolean TAG_WITH_CLASS_NAME = false;
+
+ // Default log tag for the Bubbles package.
+ static final String TAG_BUBBLES = "Bubbles";
+
+ static final boolean DEBUG_BUBBLE_CONTROLLER = false;
+ static final boolean DEBUG_BUBBLE_DATA = false;
+ static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
+ static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
index 4db1e276f431..9db371e487c7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
@@ -16,18 +16,13 @@
package com.android.systemui.bubbles;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -37,14 +32,13 @@ import com.android.systemui.R;
/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
public class BubbleDismissView extends FrameLayout {
- /** Duration for animations involving the dismiss target text/icon/gradient. */
+ /** Duration for animations involving the dismiss target text/icon. */
private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
-
- private View mDismissGradient;
+ private static final float SCALE_FOR_POP = 1.2f;
+ private static final float SCALE_FOR_DISMISS = 0.9f;
private LinearLayout mDismissTarget;
private ImageView mDismissIcon;
- private TextView mDismissText;
private View mDismissCircle;
private SpringAnimation mDismissTargetAlphaSpring;
@@ -54,36 +48,15 @@ public class BubbleDismissView extends FrameLayout {
super(context);
setVisibility(GONE);
- mDismissGradient = new FrameLayout(mContext);
-
- FrameLayout.LayoutParams gradientParams =
- new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
- gradientParams.gravity = Gravity.BOTTOM;
- mDismissGradient.setLayoutParams(gradientParams);
-
- Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
- gradient.setAlpha((int) (255 * 0.85f));
- mDismissGradient.setBackground(gradient);
-
- mDismissGradient.setVisibility(GONE);
- addView(mDismissGradient);
-
LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
- mDismissText = findViewById(R.id.bubble_dismiss_text);
mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
// Set up the basic target area animations. These are very simple animations that don't need
// fancy interpolators.
final AccelerateDecelerateInterpolator interpolator =
new AccelerateDecelerateInterpolator();
- mDismissGradient.animate()
- .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
- .setInterpolator(interpolator);
- mDismissText.animate()
- .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
- .setInterpolator(interpolator);
mDismissIcon.animate()
.setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
.setInterpolator(interpolator);
@@ -108,110 +81,58 @@ public class BubbleDismissView extends FrameLayout {
// safely assume it was animating out rather than in.
if (alpha < 0.5f) {
// If the alpha spring was animating the view out, set it to GONE when it's done.
- setVisibility(GONE);
+ setVisibility(INVISIBLE);
}
});
}
- /** Springs in the dismiss target and fades in the gradient. */
+ /** Springs in the dismiss target. */
void springIn() {
setVisibility(View.VISIBLE);
- // Fade in the dismiss target (icon + text).
+ // Fade in the dismiss target icon.
+ mDismissIcon.animate()
+ .setDuration(50)
+ .scaleX(1f)
+ .scaleY(1f)
+ .alpha(1f);
mDismissTarget.setAlpha(0f);
mDismissTargetAlphaSpring.animateToFinalPosition(1f);
- // Spring up the dismiss target (icon + text).
+ // Spring up the dismiss target.
mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
mDismissTargetVerticalSpring.animateToFinalPosition(0);
- // Fade in the gradient.
- mDismissGradient.setVisibility(VISIBLE);
- mDismissGradient.animate().alpha(1f);
-
- // Make sure the dismiss elements are in the separated position (in case we hid the target
- // while they were condensed to cover the bubbles being in the target).
- mDismissIcon.setAlpha(1f);
- mDismissIcon.setScaleX(1f);
- mDismissIcon.setScaleY(1f);
- mDismissIcon.setTranslationX(0f);
- mDismissText.setAlpha(1f);
- mDismissText.setTranslationX(0f);
+ mDismissCircle.setAlpha(0f);
+ mDismissCircle.setScaleX(SCALE_FOR_POP);
+ mDismissCircle.setScaleY(SCALE_FOR_POP);
+
+ // Fade in circle and reduce size.
+ mDismissCircle.animate()
+ .alpha(1f)
+ .scaleX(1f)
+ .scaleY(1f);
}
- /** Springs out the dismiss target and fades out the gradient. */
+ /** Springs out the dismiss target. */
void springOut() {
+ // Fade out the target icon.
+ mDismissIcon.animate()
+ .setDuration(50)
+ .scaleX(SCALE_FOR_DISMISS)
+ .scaleY(SCALE_FOR_DISMISS)
+ .alpha(0f);
+
// Fade out the target.
mDismissTargetAlphaSpring.animateToFinalPosition(0f);
// Spring the target down a bit.
mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
- // Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
- mDismissGradient.animate().alpha(0f).withEndAction(
- () -> mDismissGradient.setVisibility(GONE));
-
- // Pop out the dismiss circle.
- mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
- }
-
- /**
- * Encircles the center of the dismiss target, pulling the X towards the center and hiding the
- * text.
- */
- void animateEncircleCenterWithX(boolean encircle) {
- // Pull the text towards the center if we're encircling (it'll be faded out, leaving only
- // the X icon over the bubbles), or back to normal if we're un-encircling.
- final float textTranslation = encircle
- ? -mDismissIcon.getWidth() / 4f
- : 0f;
-
- // Center the icon if we're encircling, or put it back to normal if not.
- final float iconTranslation = encircle
- ? mDismissTarget.getWidth() / 2f
- - mDismissIcon.getWidth() / 2f
- - mDismissIcon.getLeft()
- : 0f;
-
- // Fade in/out the text and translate it.
- mDismissText.animate()
- .alpha(encircle ? 0f : 1f)
- .translationX(textTranslation);
-
- mDismissIcon.animate()
- .setDuration(150)
- .translationX(iconTranslation);
-
- // Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
- // themselves).
- mDismissGradient.animate()
- .alpha(encircle ? 0f : 1f);
-
- // Prepare the circle to be 'dropped in'.
- if (encircle) {
- mDismissCircle.setAlpha(0f);
- mDismissCircle.setScaleX(1.2f);
- mDismissCircle.setScaleY(1.2f);
- }
-
- // Drop in the circle, or pull it back up.
- mDismissCircle.animate()
- .alpha(encircle ? 1f : 0f)
- .scaleX(encircle ? 1f : 0f)
- .scaleY(encircle ? 1f : 0f);
- }
-
- /** Animates the circle and the centered icon out. */
- void animateEncirclingCircleDisappearance() {
- // Pop out the dismiss icon and circle.
- mDismissIcon.animate()
- .setDuration(50)
- .scaleX(0.9f)
- .scaleY(0.9f)
- .alpha(0f);
+ // Pop out the circle.
mDismissCircle.animate()
- .scaleX(0.9f)
- .scaleY(0.9f)
+ .scaleX(SCALE_FOR_DISMISS)
+ .scaleY(SCALE_FOR_DISMISS)
.alpha(0f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index de08a8c9c1b3..be10dc565159 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -18,14 +18,15 @@ package com.android.systemui.bubbles;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.app.ActivityView;
-import android.app.INotificationManager;
-import android.app.Notification;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -35,18 +36,17 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
import android.util.StatsLog;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowManager;
import android.widget.LinearLayout;
import com.android.internal.policy.ScreenDecorationsUtils;
@@ -54,15 +54,23 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.AlphaOptimizedButton;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
*/
public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
- private static final String TAG = "BubbleExpandedView";
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+
+ private enum ActivityViewStatus {
+ // ActivityView is being initialized, cannot start an activity yet.
+ INITIALIZING,
+ // ActivityView is initialized, and ready to start an activity.
+ INITIALIZED,
+ // Activity runs in the ActivityView.
+ ACTIVITY_STARTED,
+ // ActivityView is released, so activity launching will no longer be permitted.
+ RELEASED,
+ }
// The triangle pointing to the expanded view
private View mPointerView;
@@ -71,50 +79,89 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
private AlphaOptimizedButton mSettingsIcon;
// Views for expanded state
- private ExpandableNotificationRow mNotifRow;
private ActivityView mActivityView;
- private boolean mActivityViewReady = false;
+ private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
+ private int mTaskId = -1;
+
private PendingIntent mBubbleIntent;
private boolean mKeyboardVisible;
private boolean mNeedsNewHeight;
+ private Point mDisplaySize;
private int mMinHeight;
private int mSettingsIconHeight;
- private int mBubbleHeight;
private int mPointerWidth;
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
+ private Rect mTempRect = new Rect();
+ private int[] mTempLoc = new int[2];
+ private int mExpandedViewTouchSlop;
- private NotificationEntry mEntry;
+ private Bubble mBubble;
private PackageManager mPm;
private String mAppName;
private Drawable mAppIcon;
- private INotificationManager mNotificationManagerService;
private BubbleController mBubbleController = Dependency.get(BubbleController.class);
private BubbleStackView mStackView;
- private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
-
private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
@Override
public void onActivityViewReady(ActivityView view) {
- if (!mActivityViewReady) {
- mActivityViewReady = true;
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- 0 /* enterResId */, 0 /* exitResId */);
- // Post to keep the lifecycle normal
- post(() -> mActivityView.startActivity(mBubbleIntent, options));
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+ + " bubble=" + getBubbleKey());
+ }
+ switch (mActivityViewStatus) {
+ case INITIALIZING:
+ case INITIALIZED:
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+ 0 /* enterResId */, 0 /* exitResId */);
+ // Post to keep the lifecycle normal
+ post(() -> {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onActivityViewReady: calling startActivity, "
+ + "bubble=" + getBubbleKey());
+ }
+ try {
+ mActivityView.startActivity(mBubbleIntent, options);
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mBubbleController.removeBubble(mBubble.getKey(),
+ BubbleController.DISMISS_INVALID_INTENT);
+ }
+ });
+ mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
}
}
@Override
public void onActivityViewDestroyed(ActivityView view) {
- mActivityViewReady = false;
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
+ + " bubble=" + getBubbleKey());
+ }
+ mActivityViewStatus = ActivityViewStatus.RELEASED;
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskCreated: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ // Since Bubble ActivityView applies singleTaskDisplay this is
+ // guaranteed to only be called once per ActivityView. The taskId is
+ // saved to use for removeTask, preventing appearance in recent tasks.
+ mTaskId = taskId;
}
/**
@@ -125,9 +172,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
*/
@Override
public void onTaskRemovalStarted(int taskId) {
- if (mEntry != null) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+ + " mActivityViewStatus=" + mActivityViewStatus
+ + " bubble=" + getBubbleKey());
+ }
+ if (mBubble != null) {
// Must post because this is called from a binder thread.
- post(() -> mBubbleController.removeBubble(mEntry.key,
+ post(() -> mBubbleController.removeBubble(mBubble.getKey(),
BubbleController.DISMISS_TASK_FINISHED));
}
}
@@ -149,20 +201,22 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mPm = context.getPackageManager();
- mMinHeight = getResources().getDimensionPixelSize(
- R.dimen.bubble_expanded_default_height);
- mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
- try {
- mNotificationManagerService = INotificationManager.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
- } catch (ServiceManager.ServiceNotFoundException e) {
- Log.w(TAG, e);
- }
+ mDisplaySize = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ // Get the real size -- this includes screen decorations (notches, statusbar, navbar).
+ wm.getDefaultDisplay().getRealSize(mDisplaySize);
+ Resources res = getResources();
+ mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
+ mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+ mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
+ }
Resources res = getResources();
mPointerView = findViewById(R.id.pointer_view);
@@ -173,10 +227,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mPointerDrawable = new ShapeDrawable(TriangleShape.create(
mPointerWidth, mPointerHeight, true /* pointUp */));
mPointerView.setBackground(mPointerDrawable);
- mPointerView.setVisibility(GONE);
+ mPointerView.setVisibility(INVISIBLE);
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
- R.dimen.bubble_expanded_header_height);
+ R.dimen.bubble_settings_size);
mSettingsIcon = findViewById(R.id.settings_button);
mSettingsIcon.setOnClickListener(this);
@@ -210,6 +264,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
});
}
+ private String getBubbleKey() {
+ return mBubble != null ? mBubble.getKey() : "null";
+ }
+
void applyThemeAttrs() {
TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView);
int bgColor = ta.getColor(
@@ -235,6 +293,9 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
if (mActivityView != null) {
mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
}
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
+ }
}
/**
@@ -246,6 +307,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* and setting {@code false} actually means rendering the contents in transparent.
*/
void setContentVisibility(boolean visibility) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "setContentVisibility: visibility=" + visibility
+ + " bubble=" + getBubbleKey());
+ }
final float alpha = visibility ? 1f : 0f;
mPointerView.setAlpha(alpha);
if (mActivityView != null) {
@@ -259,44 +324,34 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
*/
void updateInsets(WindowInsets insets) {
if (usingActivityView()) {
- Point displaySize = new Point();
- mActivityView.getContext().getDisplay().getSize(displaySize);
- int[] windowLocation = mActivityView.getLocationOnScreen();
- final int windowBottom = windowLocation[1] + mActivityView.getHeight();
- final int keyboardHeight = insets.getSystemWindowInsetBottom()
- - insets.getStableInsetBottom();
- final int insetsBottom = Math.max(0,
- windowBottom + keyboardHeight - displaySize.y);
+ int[] screenLoc = mActivityView.getLocationOnScreen();
+ final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
+ final int keyboardTop = mDisplaySize.y - insets.getSystemWindowInsetBottom();
+ final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0);
mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
}
}
/**
- * Sets the listener to notify when a bubble has been blocked.
+ * Sets the bubble used to populate this view.
*/
- public void setOnBlockedListener(OnBubbleBlockedListener listener) {
- mOnBubbleBlockedListener = listener;
- }
+ public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+ }
- /**
- * Sets the notification entry used to populate this view.
- */
- public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
mStackView = stackView;
- mEntry = entry;
+ mBubble = bubble;
mAppName = appName;
- ApplicationInfo info;
try {
- info = mPm.getApplicationInfo(
- entry.notification.getPackageName(),
+ ApplicationInfo info = mPm.getApplicationInfo(
+ bubble.getPackageName(),
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- mAppIcon = mPm.getApplicationIcon(info);
- }
+ mAppIcon = mPm.getApplicationIcon(info);
} catch (PackageManager.NameNotFoundException e) {
// Do nothing.
}
@@ -312,52 +367,46 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* Lets activity view know it should be shown / populated.
*/
public void populateExpandedView() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "populateExpandedView: "
+ + "bubble=" + getBubbleKey());
+ }
+
if (usingActivityView()) {
mActivityView.setCallback(mStateCallback);
} else {
- // We're using notification template
- ViewGroup parent = (ViewGroup) mNotifRow.getParent();
- if (parent == this) {
- // Already added
- return;
- } else if (parent != null) {
- // Still in the shade... remove it
- parent.removeView(mNotifRow);
- }
- addView(mNotifRow, 1 /* index */);
- mPointerView.setAlpha(1f);
+ Log.e(TAG, "Cannot populate expanded view.");
}
}
/**
- * Updates the entry backing this view. This will not re-populate ActivityView, it will
+ * Updates the bubble backing this view. This will not re-populate ActivityView, it will
* only update the deep-links in the title, and the height of the view.
*/
- public void update(NotificationEntry entry) {
- if (entry.key.equals(mEntry.key)) {
- mEntry = entry;
+ public void update(Bubble bubble) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+ }
+ if (bubble.getKey().equals(mBubble.getKey())) {
+ mBubble = bubble;
updateSettingsContentDescription();
updateHeight();
} else {
- Log.w(TAG, "Trying to update entry with different key, new entry: "
- + entry.key + " old entry: " + mEntry.key);
+ Log.w(TAG, "Trying to update entry with different key, new bubble: "
+ + bubble.getKey() + " old bubble: " + bubble.getKey());
}
}
private void updateExpandedView() {
- mBubbleIntent = getBubbleIntent(mEntry);
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "updateExpandedView: bubble="
+ + getBubbleKey());
+ }
+
+ mBubbleIntent = mBubble.getBubbleIntent(mContext);
if (mBubbleIntent != null) {
- if (mNotifRow != null) {
- // Clear out the row if we had it previously
- removeView(mNotifRow);
- mNotifRow = null;
- }
setContentVisibility(false);
mActivityView.setVisibility(VISIBLE);
- } else if (DEBUG_ENABLE_AUTO_BUBBLE) {
- // Hide activity view if we had it previously
- mActivityView.setVisibility(GONE);
- mNotifRow = mEntry.getRow();
}
updateView();
}
@@ -371,29 +420,12 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
void updateHeight() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
+ }
if (usingActivityView()) {
- Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
- float desiredHeight;
- if (data == null) {
- // This is a contentIntent based bubble, lets allow it to be the max height
- // as it was forced into this mode and not prepared to be small
- desiredHeight = mStackView.getMaxExpandedHeight();
- } else {
- boolean useRes = data.getDesiredHeightResId() != 0;
- float desiredPx;
- if (useRes) {
- desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
- mEntry.notification.getPackageName(),
- mEntry.notification.getUser().getIdentifier());
- } else {
- desiredPx = data.getDesiredHeight()
- * getContext().getResources().getDisplayMetrics().density;
- }
- desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
- }
- int max = mStackView.getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
- - mPointerMargin;
- float height = Math.min(desiredHeight, max);
+ float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
+ float height = Math.min(desiredHeight, getMaxExpandedHeight());
height = Math.max(height, mMinHeight);
LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
mNeedsNewHeight = lp.height != height;
@@ -401,28 +433,65 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
// If the keyboard is visible... don't adjust the height because that will cause
// a configuration change and the keyboard will be lost.
lp.height = (int) height;
- mBubbleHeight = (int) height;
mActivityView.setLayoutParams(lp);
mNeedsNewHeight = false;
}
- } else {
- mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
+ + " mNeedsNewHeight=" + mNeedsNewHeight);
+ }
+ }
+ }
+
+ private int getMaxExpandedHeight() {
+ int[] windowLocation = mActivityView.getLocationOnScreen();
+ int bottomInset = getRootWindowInsets() != null
+ ? getRootWindowInsets().getStableInsetBottom()
+ : 0;
+ return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
+ - mPointerMargin - bottomInset;
+ }
+
+ /**
+ * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
+ * expanded view.
+ *
+ * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
+ */
+ boolean intersectingTouchableContent(int rawX, int rawY) {
+ mTempRect.setEmpty();
+ if (mActivityView != null) {
+ mTempLoc = mActivityView.getLocationOnScreen();
+ mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
+ mTempLoc[1] - mExpandedViewTouchSlop,
+ mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
+ mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
+ }
+ if (mTempRect.contains(rawX, rawY)) {
+ return true;
+ }
+ mTempLoc = mSettingsIcon.getLocationOnScreen();
+ mTempRect.set(mTempLoc[0],
+ mTempLoc[1],
+ mTempLoc[0] + mSettingsIcon.getWidth(),
+ mTempLoc[1] + mSettingsIcon.getHeight());
+ if (mTempRect.contains(rawX, rawY)) {
+ return true;
}
+ return false;
}
@Override
public void onClick(View view) {
- if (mEntry == null) {
+ if (mBubble == null) {
return;
}
- Notification n = mEntry.notification.getNotification();
int id = view.getId();
if (id == R.id.settings_button) {
- Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
- mEntry.notification.getUid());
+ Intent intent = mBubble.getSettingsIntent();
mStackView.collapseStack(() -> {
- mContext.startActivityAsUser(intent, mEntry.notification.getUser());
- logBubbleClickEvent(mEntry,
+ mContext.startActivityAsUser(intent, mBubble.getEntry().notification.getUser());
+ logBubbleClickEvent(mBubble,
StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
});
}
@@ -442,13 +511,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* Update appearance of the expanded view being displayed.
*/
public void updateView() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "updateView: bubble="
+ + getBubbleKey());
+ }
if (usingActivityView()
&& mActivityView.getVisibility() == VISIBLE
&& mActivityView.isAttachedToWindow()) {
mActivityView.onLocationChanged();
- } else if (mNotifRow != null) {
- applyRowState(mNotifRow);
- mPointerView.setAlpha(1f);
}
updateHeight();
}
@@ -467,17 +537,44 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* Removes and releases an ActivityView if one was previously created for this bubble.
*/
public void cleanUpExpandedState() {
- removeView(mNotifRow);
-
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
+ + ", bubble=" + getBubbleKey());
+ }
if (mActivityView == null) {
return;
}
- if (mActivityViewReady) {
- mActivityView.release();
+ switch (mActivityViewStatus) {
+ case INITIALIZED:
+ case ACTIVITY_STARTED:
+ mActivityView.release();
+ }
+ if (mTaskId != -1) {
+ try {
+ ActivityTaskManager.getService().removeTask(mTaskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove taskId " + mTaskId);
+ }
+ mTaskId = -1;
}
removeView(mActivityView);
+
mActivityView = null;
- mActivityViewReady = false;
+ }
+
+ /**
+ * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
+ * which {@link ActivityView} uses.
+ */
+ void notifyDisplayEmpty() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "notifyDisplayEmpty: bubble="
+ + getBubbleKey()
+ + " mActivityViewStatus=" + mActivityViewStatus);
+ }
+ if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
+ mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+ }
}
private boolean usingActivityView() {
@@ -494,76 +591,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
return INVALID_DISPLAY;
}
- private void applyRowState(ExpandableNotificationRow view) {
- view.reset();
- view.setHeadsUp(false);
- view.resetTranslation();
- view.setOnKeyguard(false);
- view.setClipBottomAmount(0);
- view.setClipTopAmount(0);
- view.setContentTransformationAmount(0, false);
- view.setIconsVisible(true);
-
- // TODO - Need to reset this (and others) when view goes back in shade, leave for now
- // view.setTopRoundness(1, false);
- // view.setBottomRoundness(1, false);
-
- ExpandableViewState viewState = view.getViewState();
- viewState = viewState == null ? new ExpandableViewState() : viewState;
- viewState.height = view.getIntrinsicHeight();
- viewState.gone = false;
- viewState.hidden = false;
- viewState.dimmed = false;
- viewState.alpha = 1f;
- viewState.notGoneIndex = -1;
- viewState.xTranslation = 0;
- viewState.yTranslation = 0;
- viewState.zTranslation = 0;
- viewState.scaleX = 1;
- viewState.scaleY = 1;
- viewState.inShelf = true;
- viewState.headsUpIsVisible = false;
- viewState.applyToView(view);
- }
-
- private Intent getSettingsIntent(String packageName, final int appUid) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- return intent;
- }
-
- @Nullable
- private PendingIntent getBubbleIntent(NotificationEntry entry) {
- Notification notif = entry.notification.getNotification();
- Notification.BubbleMetadata data = notif.getBubbleMetadata();
- if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) {
- return data.getIntent();
- }
- return null;
- }
-
- /**
- * Listener that is notified when a bubble is blocked.
- */
- public interface OnBubbleBlockedListener {
- /**
- * Called when a bubble is blocked for the provided entry.
- */
- void onBubbleBlocked(NotificationEntry entry);
- }
-
/**
* Logs bubble UI click event.
*
- * @param entry the bubble notification entry that user is interacting with.
+ * @param bubble the bubble notification entry that user is interacting with.
* @param action the user interaction enum.
*/
- private void logBubbleClickEvent(NotificationEntry entry, int action) {
- StatusBarNotification notification = entry.notification;
+ private void logBubbleClickEvent(Bubble bubble, int action) {
+ StatusBarNotification notification = bubble.getEntry().notification;
StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
notification.getPackageName(),
notification.getNotification().getChannelId(),
@@ -573,27 +608,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
action,
mStackView.getNormalizedXPosition(),
mStackView.getNormalizedYPosition(),
- entry.showInShadeWhenBubble(),
- entry.isForegroundService(),
- BubbleController.isForegroundApp(mContext, notification.getPackageName()));
- }
-
- private int getDimenForPackageUser(int resId, String pkg, int userId) {
- Resources r;
- if (pkg != null) {
- try {
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- }
- r = mPm.getResourcesForApplicationAsUser(pkg, userId);
- return r.getDimensionPixelSize(resId);
- } catch (PackageManager.NameNotFoundException ex) {
- // Uninstalled, don't care
- } catch (Resources.NotFoundException e) {
- // Invalid res id, return 0 and user our default
- Log.e(TAG, "Couldn't find desired height res id", e);
- }
- }
- return 0;
+ bubble.showInShadeWhenBubble(),
+ bubble.isOngoing(),
+ false /* isAppForeground (unused) */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 71f68c16bd8d..58f3f2211d81 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -39,8 +39,7 @@ import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.TextView;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.annotation.Nullable;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
@@ -57,6 +56,9 @@ public class BubbleFlyoutView extends FrameLayout {
private final int mFlyoutSpaceFromBubble;
private final int mPointerSize;
private final int mBubbleSize;
+ private final int mBubbleIconBitmapSize;
+ private final float mBubbleIconTopPadding;
+
private final int mFlyoutElevation;
private final int mBubbleElevation;
private final int mFloatingBackgroundColor;
@@ -64,14 +66,11 @@ public class BubbleFlyoutView extends FrameLayout {
private final ViewGroup mFlyoutTextContainer;
private final TextView mFlyoutText;
- /** Spring animation for the flyout. */
- private final SpringAnimation mFlyoutSpring =
- new SpringAnimation(this, DynamicAnimation.TRANSLATION_X);
/** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
private final float mNewDotRadius;
private final float mNewDotSize;
- private final float mNewDotOffsetFromBubbleBounds;
+ private final float mOriginalDotSize;
/**
* The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -113,7 +112,6 @@ public class BubbleFlyoutView extends FrameLayout {
*/
private float mFlyoutToDotWidthDelta = 0f;
private float mFlyoutToDotHeightDelta = 0f;
- private float mFlyoutToDotCornerRadiusDelta;
/** The translation values when the flyout is completely transitioned into the dot. */
private float mTranslationXWhenDot = 0f;
@@ -126,11 +124,18 @@ public class BubbleFlyoutView extends FrameLayout {
private float mBgTranslationX;
private float mBgTranslationY;
+ private float[] mDotCenter;
+
/** The flyout's X translation when at rest (not animating or dragging). */
private float mRestingTranslationX = 0f;
+ /** The badge sizes are defined as percentages of the app icon size. Same value as Launcher3. */
+ private static final float SIZE_PERCENTAGE = 0.228f;
+
+ private static final float DOT_SCALE = 1f;
+
/** Callback to run when the flyout is hidden. */
- private Runnable mOnHide;
+ @Nullable private Runnable mOnHide;
public BubbleFlyoutView(Context context) {
super(context);
@@ -143,11 +148,16 @@ public class BubbleFlyoutView extends FrameLayout {
mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
+
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+ mBubbleIconTopPadding = (mBubbleSize - mBubbleIconBitmapSize) / 2f;
+
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
- mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
- mNewDotRadius = BadgeRenderer.getDotRadius(mNewDotOffsetFromBubbleBounds);
+
+ mOriginalDotSize = SIZE_PERCENTAGE * mBubbleIconBitmapSize;
+ mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
mNewDotSize = mNewDotRadius * 2f;
final TypedArray ta = mContext.obtainStyledAttributes(
@@ -156,7 +166,6 @@ public class BubbleFlyoutView extends FrameLayout {
android.R.attr.dialogCornerRadius});
mFloatingBackgroundColor = ta.getColor(0, Color.WHITE);
mCornerRadius = ta.getDimensionPixelSize(1, 0);
- mFlyoutToDotCornerRadiusDelta = mNewDotRadius - mCornerRadius;
ta.recycle();
// Add padding for the pointer on either side, onDraw will draw it in this space.
@@ -193,17 +202,17 @@ public class BubbleFlyoutView extends FrameLayout {
super.onDraw(canvas);
}
- /** Configures the flyout and animates it in. */
- void showFlyout(
+ /** Configures the flyout, collapsed into to dot form. */
+ void setupFlyoutStartingAsDot(
CharSequence updateMessage, PointF stackPos, float parentWidth,
- boolean arrowPointingLeft, int dotColor, Runnable onHide) {
+ boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete,
+ @Nullable Runnable onHide, float[] dotCenter) {
mArrowPointingLeft = arrowPointingLeft;
mDotColor = dotColor;
mOnHide = onHide;
+ mDotCenter = dotCenter;
- setCollapsePercent(0f);
- setAlpha(0f);
- setVisibility(VISIBLE);
+ setCollapsePercent(1f);
// Set the flyout TextView's max width in terms of percent, and then subtract out the
// padding so that the entire flyout view will be the desired width (rather than the
@@ -214,14 +223,16 @@ public class BubbleFlyoutView extends FrameLayout {
// Wait for the TextView to lay out so we know its line count.
post(() -> {
+ float restingTranslationY;
// Multi line flyouts get top-aligned to the bubble.
if (mFlyoutText.getLineCount() > 1) {
- setTranslationY(stackPos.y);
+ restingTranslationY = stackPos.y + mBubbleIconTopPadding;
} else {
// Single line flyouts are vertically centered with respect to the bubble.
- setTranslationY(
- stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f);
+ restingTranslationY =
+ stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
}
+ setTranslationY(restingTranslationY);
// Calculate the translation required to position the flyout next to the bubble stack,
// with the desired padding.
@@ -229,40 +240,30 @@ public class BubbleFlyoutView extends FrameLayout {
? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
: stackPos.x - getWidth() - mFlyoutSpaceFromBubble;
- // Translate towards the stack slightly.
- setTranslationX(
- mRestingTranslationX + (arrowPointingLeft ? -mBubbleSize : mBubbleSize));
-
- // Fade in the entire flyout and spring it to its normal position.
- animate().alpha(1f);
- mFlyoutSpring.animateToFinalPosition(mRestingTranslationX);
-
// Calculate the difference in size between the flyout and the 'dot' so that we can
// transform into the dot later.
mFlyoutToDotWidthDelta = getWidth() - mNewDotSize;
mFlyoutToDotHeightDelta = getHeight() - mNewDotSize;
// Calculate the translation values needed to be in the correct 'new dot' position.
- final float distanceFromFlyoutLeftToDotCenterX =
- mFlyoutSpaceFromBubble + mNewDotOffsetFromBubbleBounds / 2;
- if (mArrowPointingLeft) {
- mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
- } else {
- mTranslationXWhenDot =
- getWidth() + distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
- }
+ final float dotPositionX = stackPos.x + mDotCenter[0] - (mOriginalDotSize / 2f);
+ final float dotPositionY = stackPos.y + mDotCenter[1] - (mOriginalDotSize / 2f);
+
+ final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
+ final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY;
- mTranslationYWhenDot =
- getHeight() / 2f
- - mNewDotRadius
- - mBubbleSize / 2f
- + mNewDotOffsetFromBubbleBounds / 2;
+ mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
+ mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
+ if (onLayoutComplete != null) {
+ onLayoutComplete.run();
+ }
});
}
/**
- * Hides the flyout and runs the optional callback passed into showFlyout. The flyout has been
- * animated into the 'new' dot by the time we call this, so no animations are needed.
+ * Hides the flyout and runs the optional callback passed into setupFlyoutStartingAsDot.
+ * The flyout has been animated into the 'new' dot by the time we call this, so no animations
+ * are needed.
*/
void hideFlyout() {
if (mOnHide != null) {
@@ -275,6 +276,13 @@ public class BubbleFlyoutView extends FrameLayout {
/** Sets the percentage that the flyout should be collapsed into dot form. */
void setCollapsePercent(float percentCollapsed) {
+ // This is unlikely, but can happen in a race condition where the flyout view hasn't been
+ // laid out and returns 0 for getWidth(). We check for this condition at the sites where
+ // this method is called, but better safe than sorry.
+ if (Float.isNaN(percentCollapsed)) {
+ return;
+ }
+
mPercentTransitionedToDot = Math.max(0f, Math.min(percentCollapsed, 1f));
mPercentStillFlyout = (1f - mPercentTransitionedToDot);
@@ -311,8 +319,8 @@ public class BubbleFlyoutView extends FrameLayout {
// percentage.
final float width = getWidth() - (mFlyoutToDotWidthDelta * mPercentTransitionedToDot);
final float height = getHeight() - (mFlyoutToDotHeightDelta * mPercentTransitionedToDot);
- final float cornerRadius = mCornerRadius
- - (mFlyoutToDotCornerRadiusDelta * mPercentTransitionedToDot);
+ final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot
+ + mCornerRadius * (1 - mPercentTransitionedToDot);
// Translate the flyout background towards the collapsed 'dot' state.
mBgTranslationX = mTranslationXWhenDot * mPercentTransitionedToDot;
@@ -336,7 +344,7 @@ public class BubbleFlyoutView extends FrameLayout {
canvas.save();
canvas.translate(mBgTranslationX, mBgTranslationY);
renderPointerTriangle(canvas, width, height);
- canvas.drawRoundRect(mBgRect, cornerRadius, cornerRadius, mBgPaint);
+ canvas.drawRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, mBgPaint);
canvas.restore();
}
@@ -379,7 +387,10 @@ public class BubbleFlyoutView extends FrameLayout {
if (!mTriangleOutline.isEmpty()) {
// Draw the rect into the outline as a path so we can merge the triangle path into it.
final Path rectPath = new Path();
- rectPath.addRoundRect(mBgRect, mCornerRadius, mCornerRadius, Path.Direction.CW);
+ final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot
+ + mCornerRadius * (1 - mPercentTransitionedToDot);
+ rectPath.addRoundRect(mBgRect, interpolatedRadius,
+ interpolatedRadius, Path.Direction.CW);
outline.setConvexPath(rectPath);
// Get rid of the triangle path once it has disappeared behind the flyout.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
new file mode 100644
index 000000000000..a1c77c0af6bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.systemui.R;
+
+/**
+ * Factory for creating normalized bubble icons.
+ * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
+ * so there is no need to manage a pool across multiple threads.
+ */
+public class BubbleIconFactory extends BaseIconFactory {
+ protected BubbleIconFactory(Context context) {
+ super(context, context.getResources().getConfiguration().densityDpi,
+ context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
+ }
+
+ public int getBadgeSize() {
+ return mContext.getResources().getDimensionPixelSize(
+ com.android.launcher3.icons.R.dimen.profile_badge_size);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f87bcef5fde2..13d6470a351f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,16 +19,20 @@ package com.android.systemui.bubbles;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.Notification;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
@@ -45,7 +49,6 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -69,6 +72,8 @@ import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
@@ -79,8 +84,7 @@ import java.util.List;
* Renders bubbles in a stack and handles animating expanded and collapsed states.
*/
public class BubbleStackView extends FrameLayout {
- private static final String TAG = "BubbleStackView";
- private static final boolean DEBUG = false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -160,9 +164,14 @@ public class BubbleStackView extends FrameLayout {
private BubbleFlyoutView mFlyout;
/** Runnable that fades out the flyout and then sets it to GONE. */
private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
+ /**
+ * Callback to run after the flyout hides. Also called if a new flyout is shown before the
+ * previous one animates out.
+ */
+ private Runnable mAfterFlyoutHides;
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
- private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
+ private OnLayoutChangeListener mOrientationChangedListener;
/** Whether the stack was on the left side of the screen prior to rotation. */
private boolean mWasOnLeftBeforeRotation = false;
/**
@@ -172,18 +181,17 @@ public class BubbleStackView extends FrameLayout {
private float mVerticalPosPercentBeforeRotation = -1;
private int mBubbleSize;
- private int mBubblePadding;
+ private int mBubblePaddingTop;
+ private int mBubbleTouchPadding;
private int mExpandedViewPadding;
private int mExpandedAnimateXDistance;
private int mExpandedAnimateYDistance;
private int mPointerHeight;
private int mStatusBarHeight;
- private int mPipDismissHeight;
private int mImeOffset;
-
+ private BubbleIconFactory mBubbleIconFactory;
private Bubble mExpandedBubble;
private boolean mIsExpanded;
- private boolean mImeVisible;
/** Whether the stack is currently on the left side of the screen, or animating there. */
private boolean mStackOnLeftOrWillBe = false;
@@ -191,9 +199,20 @@ public class BubbleStackView extends FrameLayout {
/** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
private boolean mIsGestureInProgress = false;
+ /** Description of current animation controller state. */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Stack view state:");
+ pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
+ pw.print(" showingDismiss: "); pw.println(mShowingDismiss);
+ pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
+ pw.print(" draggingInDismiss: "); pw.println(mDraggingInDismissTarget);
+ pw.print(" animatingMagnet: "); pw.println(mAnimatingMagnet);
+ mStackAnimationController.dump(fd, pw, args);
+ mExpandedAnimationController.dump(fd, pw, args);
+ }
+
private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
- private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
private boolean mViewUpdatedRequested = false;
private boolean mIsExpansionAnimating = false;
@@ -225,7 +244,7 @@ public class BubbleStackView extends FrameLayout {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
- applyCurrentState();
+ updateExpandedView();
mViewUpdatedRequested = false;
return true;
}
@@ -270,6 +289,11 @@ public class BubbleStackView extends FrameLayout {
private float mFlyoutDragDeltaX = 0f;
/**
+ * Runnable that animates in the flyout. This reference is needed to cancel delayed postings.
+ */
+ private Runnable mAnimateInFlyout;
+
+ /**
* End listener for the flyout spring that either posts a runnable to hide the flyout, or hides
* it immediately.
*/
@@ -282,13 +306,13 @@ public class BubbleStackView extends FrameLayout {
}
};
- @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
+ @NonNull
+ private final SurfaceSynchronizer mSurfaceSynchronizer;
private BubbleDismissView mDismissContainer;
private Runnable mAfterMagnet;
- private boolean mSuppressNewDot = false;
- private boolean mSuppressFlyout = false;
+ private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer) {
@@ -302,7 +326,8 @@ public class BubbleStackView extends FrameLayout {
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
mExpandedAnimateXDistance =
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
mExpandedAnimateYDistance =
@@ -311,8 +336,6 @@ public class BubbleStackView extends FrameLayout {
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- mPipDismissHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.pip_dismiss_gradient_height);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
mDisplaySize = new Point();
@@ -325,8 +348,9 @@ public class BubbleStackView extends FrameLayout {
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mStackAnimationController = new StackAnimationController();
+
mExpandedAnimationController = new ExpandedAnimationController(
- mDisplaySize, mExpandedViewPadding);
+ mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
mBubbleContainer = new PhysicsAnimationLayout(context);
@@ -335,6 +359,8 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mBubbleIconFactory = new BubbleIconFactory(context);
+
mExpandedViewContainer = new FrameLayout(context);
mExpandedViewContainer.setElevation(elevation);
mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
@@ -342,15 +368,9 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
- mFlyout = new BubbleFlyoutView(context);
- mFlyout.setVisibility(GONE);
- mFlyout.animate()
- .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
- .setInterpolator(new AccelerateDecelerateInterpolator());
- addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-
+ setUpFlyout();
mFlyoutTransitionSpring.setSpring(new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
@@ -361,13 +381,6 @@ public class BubbleStackView extends FrameLayout {
Gravity.BOTTOM));
addView(mDismissContainer);
- mDismissContainer = new BubbleDismissView(mContext);
- mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
- MATCH_PARENT,
- getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
- Gravity.BOTTOM));
- addView(mDismissContainer);
-
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
mExpandedViewXAnim.setSpring(
@@ -383,7 +396,7 @@ public class BubbleStackView extends FrameLayout {
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
if (mIsExpanded && mExpandedBubble != null) {
- mExpandedBubble.expandedView.updateView();
+ mExpandedBubble.getExpandedView().updateView();
}
});
@@ -392,34 +405,30 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.bringToFront();
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
- final int keyboardHeight = insets.getSystemWindowInsetBottom()
- - insets.getStableInsetBottom();
if (!mIsExpanded || mIsExpansionAnimating) {
return view.onApplyWindowInsets(insets);
}
- mImeVisible = keyboardHeight != 0;
-
- float newY = getYPositionForExpandedView();
- if (newY < 0) {
- // TODO: This means our expanded content is too big to fit on screen. Right now
- // we'll let it translate off but we should be clipping it & pushing the header
- // down so that it always remains visible.
- }
- mExpandedViewYAnim.animateToFinalPosition(newY);
mExpandedAnimationController.updateYPosition(
// Update the insets after we're done translating otherwise position
// calculation for them won't be correct.
- () -> mExpandedBubble.expandedView.updateInsets(insets));
+ () -> mExpandedBubble.getExpandedView().updateInsets(insets));
return view.onApplyWindowInsets(insets);
});
- mMoveStackToValidPositionOnLayoutListener =
+ mOrientationChangedListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mExpandedAnimationController.updateOrientation(mOrientation);
+ if (mIsExpanded) {
+ // Re-draw bubble row and pointer for new orientation.
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition();
+ } /* after */);
+ }
if (mVerticalPosPercentBeforeRotation >= 0) {
mStackAnimationController.moveStackToSimilarPositionAfterRotation(
mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
}
- removeOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
+ removeOnLayoutChangeListener(mOrientationChangedListener);
};
// This must be a separate OnDrawListener since it should be called for every draw.
@@ -449,25 +458,48 @@ public class BubbleStackView extends FrameLayout {
});
}
+ private void setUpFlyout() {
+ if (mFlyout != null) {
+ removeView(mFlyout);
+ }
+ mFlyout = new BubbleFlyoutView(getContext());
+ mFlyout.setVisibility(GONE);
+ mFlyout.animate()
+ .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
+ .setInterpolator(new AccelerateDecelerateInterpolator());
+ addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ }
+
/**
* Handle theme changes.
*/
public void onThemeChanged() {
+ // Recreate icon factory to update default adaptive icon scale.
+ mBubbleIconFactory = new BubbleIconFactory(mContext);
+ setUpFlyout();
for (Bubble b: mBubbleData.getBubbles()) {
- b.iconView.updateViews();
- b.expandedView.applyThemeAttrs();
+ b.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+ b.getIconView().updateViews();
+ b.getExpandedView().applyThemeAttrs();
}
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
- public void onOrientationChanged() {
+ public void onOrientationChanged(int orientation) {
+ mOrientation = orientation;
+
+ // Some resources change depending on orientation
+ Resources res = getContext().getResources();
+ mStatusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+
final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
mVerticalPosPercentBeforeRotation =
(mStackAnimationController.getStackPosition().y - allowablePos.top)
/ (allowablePos.bottom - allowablePos.top);
- addOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
-
+ addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
}
@@ -483,18 +515,6 @@ public class BubbleStackView extends FrameLayout {
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- float x = ev.getRawX();
- float y = ev.getRawY();
- // If we're expanded only intercept if the tap is outside of the widget container
- if (mIsExpanded && isIntersecting(mExpandedViewContainer, x, y)) {
- return false;
- } else {
- return isIntersecting(mBubbleContainer, x, y);
- }
- }
-
- @Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
@@ -570,7 +590,7 @@ public class BubbleStackView extends FrameLayout {
}
Bubble topBubble = mBubbleData.getBubbles().get(0);
String appName = topBubble.getAppName();
- Notification notification = topBubble.entry.notification.getNotification();
+ Notification notification = topBubble.getEntry().notification.getNotification();
CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
String titleStr = getResources().getString(R.string.stream_notification);
if (titleCharSeq != null) {
@@ -616,6 +636,7 @@ public class BubbleStackView extends FrameLayout {
/**
* Updates the visibility of the 'dot' indicating an update on the bubble.
+ *
* @param key the {@link NotificationEntry#key} associated with the bubble.
*/
public void updateDotVisibility(String key) {
@@ -640,10 +661,17 @@ public class BubbleStackView extends FrameLayout {
}
/**
+ * Whether the stack of bubbles is animating to or from expansion.
+ */
+ public boolean isExpansionAnimating() {
+ return mIsExpansionAnimating;
+ }
+
+ /**
* The {@link BubbleView} that is expanded, null if one does not exist.
*/
BubbleView getExpandedBubbleView() {
- return mExpandedBubble != null ? mExpandedBubble.iconView : null;
+ return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
}
/**
@@ -664,36 +692,33 @@ public class BubbleStackView extends FrameLayout {
Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
if (bubbleToExpand != null) {
setSelectedBubble(bubbleToExpand);
- bubbleToExpand.entry.setShowInShadeWhenBubble(false);
+ bubbleToExpand.setShowInShadeWhenBubble(false);
setExpanded(true);
}
}
- /**
- * Sets the entry that should be expanded and expands if needed.
- */
- @VisibleForTesting
- void setExpandedBubble(NotificationEntry entry) {
- for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
- BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
- if (entry.equals(bv.getEntry())) {
- setExpandedBubble(entry.key);
- }
- }
- }
-
// via BubbleData.Listener
void addBubble(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "addBubble: " + bubble);
}
+
+ if (mBubbleContainer.getChildCount() == 0) {
+ mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
+ }
+
bubble.inflate(mInflater, this);
- mBubbleContainer.addView(bubble.iconView, 0,
+ bubble.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+ bubble.getIconView().updateViews();
+
+ // Set the dot position to the opposite of the side the stack is resting on, since the stack
+ // resting slightly off-screen would result in the dot also being off-screen.
+ bubble.getIconView().setDotPosition(
+ !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
+
+ mBubbleContainer.addView(bubble.getIconView(), 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
- if (bubble.iconView != null) {
- bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */);
- }
+ ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
animateInFlyoutForBubble(bubble);
requestUpdate();
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -702,13 +727,14 @@ public class BubbleStackView extends FrameLayout {
// via BubbleData.Listener
void removeBubble(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "removeBubble: " + bubble);
}
// Remove it from the views
- int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
+ int removedIndex = mBubbleContainer.indexOfChild(bubble.getIconView());
if (removedIndex >= 0) {
mBubbleContainer.removeViewAt(removedIndex);
+ bubble.cleanupExpandedState();
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -726,8 +752,10 @@ public class BubbleStackView extends FrameLayout {
public void updateBubbleOrder(List<Bubble> bubbles) {
for (int i = 0; i < bubbles.size(); i++) {
Bubble bubble = bubbles.get(i);
- mBubbleContainer.reorderView(bubble.iconView, i);
+ mBubbleContainer.reorderView(bubble.getIconView(), i);
}
+
+ updateBubbleZOrdersAndDotPosition(false /* animate */);
}
/**
@@ -737,7 +765,7 @@ public class BubbleStackView extends FrameLayout {
*/
// via BubbleData.Listener
public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
}
if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
@@ -760,9 +788,8 @@ public class BubbleStackView extends FrameLayout {
requestUpdate();
logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- notifyExpansionChanged(previouslySelected.entry, false /* expanded */);
- notifyExpansionChanged(bubbleToSelect == null ? null : bubbleToSelect.entry,
- true /* expanded */);
+ notifyExpansionChanged(previouslySelected, false /* expanded */);
+ notifyExpansionChanged(bubbleToSelect, true /* expanded */);
});
}
}
@@ -774,42 +801,32 @@ public class BubbleStackView extends FrameLayout {
*/
// via BubbleData.Listener
public void setExpanded(boolean shouldExpand) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "setExpanded: " + shouldExpand);
}
- boolean wasExpanded = mIsExpanded;
- if (shouldExpand == wasExpanded) {
+ if (shouldExpand == mIsExpanded) {
return;
}
- if (wasExpanded) {
- // Collapse the stack
- mExpandedViewContainer.setAlpha(0.0f);
- // TODO: In order to prevent flicker, code below should be executed after the alpha
- // value set on the mExpandedViewContainer is reflected on the screen. However, we
- // cannot just postpone the execution like #setSelectedBubble(), since some of member
- // variables referred by the code are overridden before the execution.
- if (mExpandedBubble != null) {
- mExpandedBubble.setContentVisibility(false);
- }
- animateExpansion(false /* expand */);
+ if (mIsExpanded) {
+ animateCollapse();
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
- // Expand the stack
- animateExpansion(true /* expand */);
+ animateExpansion();
// TODO: move next line to BubbleData
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
}
- notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded);
+ notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
/**
* Dismiss the stack of bubbles.
+ *
* @deprecated
*/
@Deprecated
void stackDismissed(int reason) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "stackDismissed: reason=" + reason);
}
mBubbleData.dismissAll(reason);
@@ -826,21 +843,23 @@ public class BubbleStackView extends FrameLayout {
float y = event.getRawY();
if (mIsExpanded) {
if (isIntersecting(mBubbleContainer, x, y)) {
+ // Could be tapping or dragging a bubble while expanded
for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
BubbleView view = (BubbleView) mBubbleContainer.getChildAt(i);
if (isIntersecting(view, x, y)) {
return view;
}
}
- } else if (isIntersecting(mExpandedViewContainer, x, y)) {
- return mExpandedViewContainer;
}
- // Outside parts of view we care about.
+ BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
+ if (bev.intersectingTouchableContent((int) x, (int) y)) {
+ return bev;
+ }
+ // Outside of the parts we care about.
return null;
} else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
return mFlyout;
}
-
// If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
return this;
}
@@ -859,7 +878,7 @@ public class BubbleStackView extends FrameLayout {
@Deprecated
@MainThread
void collapseStack() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "collapseStack()");
}
mBubbleData.setExpanded(false);
@@ -871,7 +890,7 @@ public class BubbleStackView extends FrameLayout {
@Deprecated
@MainThread
void collapseStack(Runnable endRunnable) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "collapseStack(endRunnable)");
}
collapseStack();
@@ -889,73 +908,83 @@ public class BubbleStackView extends FrameLayout {
@Deprecated
@MainThread
void expandStack() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "expandStack()");
}
mBubbleData.setExpanded(true);
}
- /**
- * Tell the stack to animate to collapsed or expanded state.
- */
- private void animateExpansion(boolean shouldExpand) {
- if (DEBUG) {
- Log.d(TAG, "animateExpansion: shouldExpand=" + shouldExpand);
- }
- if (mIsExpanded != shouldExpand) {
- hideFlyoutImmediate();
+ private void beforeExpandedViewAnimation() {
+ hideFlyoutImmediate();
+ updateExpandedBubble();
+ updateExpandedView();
+ mIsExpansionAnimating = true;
+ }
- mIsExpanded = shouldExpand;
- updateExpandedBubble();
- applyCurrentState();
+ private void afterExpandedViewAnimation() {
+ updateExpandedView();
+ mIsExpansionAnimating = false;
+ requestUpdate();
+ }
- mIsExpansionAnimating = true;
+ private void animateCollapse() {
+ mIsExpanded = false;
+ final Bubble previouslySelected = mExpandedBubble;
+ beforeExpandedViewAnimation();
+
+ mBubbleContainer.cancelAllAnimations();
+ mExpandedAnimationController.collapseBackToStack(
+ mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+ /* collapseTo */,
+ () -> {
+ mBubbleContainer.setActiveController(mStackAnimationController);
+ afterExpandedViewAnimation();
+ previouslySelected.setContentVisibility(false);
+ });
- Runnable updateAfter = () -> {
- applyCurrentState();
- mIsExpansionAnimating = false;
- requestUpdate();
- };
+ mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
+ mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
+ mExpandedViewContainer.animate()
+ .setDuration(100)
+ .alpha(0f);
+ }
- if (shouldExpand) {
- mBubbleContainer.setActiveController(mExpandedAnimationController);
- mExpandedAnimationController.expandFromStack(() -> {
- updatePointerPosition();
- updateAfter.run();
- } /* after */);
- } else {
- mBubbleContainer.cancelAllAnimations();
- mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
- () -> {
- mBubbleContainer.setActiveController(mStackAnimationController);
- updateAfter.run();
- });
- }
+ private void animateExpansion() {
+ mIsExpanded = true;
+ beforeExpandedViewAnimation();
- final float xStart =
- mStackAnimationController.getStackPosition().x < getWidth() / 2
- ? -mExpandedAnimateXDistance
- : mExpandedAnimateXDistance;
+ mBubbleContainer.setActiveController(mExpandedAnimationController);
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition();
+ afterExpandedViewAnimation();
+ } /* after */);
- final float yStart = Math.min(
- mStackAnimationController.getStackPosition().y,
- mExpandedAnimateYDistance);
- final float yDest = getYPositionForExpandedView();
- if (shouldExpand) {
- mExpandedViewContainer.setTranslationX(xStart);
- mExpandedViewContainer.setTranslationY(yStart);
- }
+ mExpandedViewContainer.setTranslationX(getCollapsedX());
+ mExpandedViewContainer.setTranslationY(getCollapsedY());
+ mExpandedViewContainer.setAlpha(0f);
- mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
- mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
- }
+ mExpandedViewXAnim.animateToFinalPosition(0f);
+ mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
+ mExpandedViewContainer.animate()
+ .setDuration(100)
+ .alpha(1f);
+ }
+
+ private float getCollapsedX() {
+ return mStackAnimationController.getStackPosition().x < getWidth() / 2
+ ? -mExpandedAnimateXDistance
+ : mExpandedAnimateXDistance;
+ }
+
+ private float getCollapsedY() {
+ return Math.min(mStackAnimationController.getStackPosition().y,
+ mExpandedAnimateYDistance);
}
- private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
- if (mExpandListener != null) {
- mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+ private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
+ if (mExpandListener != null && bubble != null) {
+ mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
}
}
@@ -968,7 +997,7 @@ public class BubbleStackView extends FrameLayout {
/** Moves the bubbles out of the way if they're going to be over the keyboard. */
public void onImeVisibilityChanged(boolean visible, int height) {
- mStackAnimationController.setImeHeight(height + mImeOffset);
+ mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
if (!mIsExpanded) {
mStackAnimationController.animateForImeVisibility(visible);
@@ -977,7 +1006,7 @@ public class BubbleStackView extends FrameLayout {
/** Called when a drag operation on an individual bubble has started. */
public void onBubbleDragStart(View bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
}
mExpandedAnimationController.prepareForBubbleDrag(bubble);
@@ -996,7 +1025,7 @@ public class BubbleStackView extends FrameLayout {
/** Called when a drag operation on an individual bubble has finished. */
public void onBubbleDragFinish(
View bubble, float x, float y, float velX, float velY) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
}
@@ -1005,11 +1034,11 @@ public class BubbleStackView extends FrameLayout {
}
mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
- springOutDismissTargetAndHideCircle();
+ hideDismissTarget();
}
void onDragStart() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onDragStart()");
}
if (mIsExpanded || mIsExpansionAnimating) {
@@ -1033,7 +1062,7 @@ public class BubbleStackView extends FrameLayout {
}
void onDragFinish(float x, float y, float velX, float velY) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onDragFinish");
}
@@ -1046,8 +1075,8 @@ public class BubbleStackView extends FrameLayout {
StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
mStackOnLeftOrWillBe = newStackX <= 0;
- updateBubbleShadowsAndDotPosition(true /* animate */);
- springOutDismissTargetAndHideCircle();
+ updateBubbleZOrdersAndDotPosition(true /* animate */);
+ hideDismissTarget();
}
void onFlyoutDragStart() {
@@ -1055,6 +1084,12 @@ public class BubbleStackView extends FrameLayout {
}
void onFlyoutDragged(float deltaX) {
+ // This shouldn't happen, but if it does, just wait until the flyout lays out. This method
+ // is continually called.
+ if (mFlyout.getWidth() <= 0) {
+ return;
+ }
+
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
mFlyoutDragDeltaX = deltaX;
@@ -1062,7 +1097,7 @@ public class BubbleStackView extends FrameLayout {
onLeft ? -deltaX / mFlyout.getWidth() : deltaX / mFlyout.getWidth();
mFlyout.setCollapsePercent(Math.min(1f, Math.max(0f, collapsePercent)));
- // Calculate how to translate the flyout if it has been dragged too far in etiher direction.
+ // Calculate how to translate the flyout if it has been dragged too far in either direction.
float overscrollTranslation = 0f;
if (collapsePercent < 0f || collapsePercent > 1f) {
// Whether we are more than 100% transitioned to the dot.
@@ -1073,7 +1108,6 @@ public class BubbleStackView extends FrameLayout {
// after it has already become the dot.
final boolean overscrollingLeft =
(onLeft && collapsePercent > 1f) || (!onLeft && collapsePercent < 0f);
-
overscrollTranslation =
(overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
* (overscrollingLeft ? -1 : 1)
@@ -1086,6 +1120,19 @@ public class BubbleStackView extends FrameLayout {
}
/**
+ * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
+ * once it collapses.
+ */
+ @Nullable private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
+
+ void onFlyoutTapped() {
+ mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+
+ mFlyout.removeCallbacks(mHideFlyout);
+ mHideFlyout.run();
+ }
+
+ /**
* Called when the flyout drag has finished, and returns true if the gesture successfully
* dismissed the flyout.
*/
@@ -1181,9 +1228,6 @@ public class BubbleStackView extends FrameLayout {
animateDesaturateAndDarken(magnetView, true);
}
-
- mDismissContainer.animateEncircleCenterWithX(true);
-
} else {
mAnimatingMagnet = false;
@@ -1194,8 +1238,6 @@ public class BubbleStackView extends FrameLayout {
mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
animateDesaturateAndDarken(magnetView, false);
}
-
- mDismissContainer.animateEncircleCenterWithX(false);
}
mVibrator.vibrate(VibrationEffect.get(toTarget
@@ -1214,7 +1256,7 @@ public class BubbleStackView extends FrameLayout {
mAfterMagnet = null;
mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
- mDismissContainer.animateEncirclingCircleDisappearance();
+ mDismissContainer.springOut();
// 'Implode' the stack and then hide the dismiss target.
if (touchedView == this) {
@@ -1252,7 +1294,7 @@ public class BubbleStackView extends FrameLayout {
}
}
- /** Animates in the dismiss target, including the gradient behind it. */
+ /** Animates in the dismiss target. */
private void springInDismissTarget() {
if (mShowingDismiss) {
return;
@@ -1270,7 +1312,7 @@ public class BubbleStackView extends FrameLayout {
* Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
* were dragged into the target and encircled.
*/
- private void springOutDismissTargetAndHideCircle() {
+ private void hideDismissTarget() {
if (!mShowingDismiss) {
return;
}
@@ -1287,6 +1329,12 @@ public class BubbleStackView extends FrameLayout {
/** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
private void animateFlyoutCollapsed(boolean collapsed, float velX) {
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+ // If the flyout was tapped, we want a higher stiffness for the collapse animation so it's
+ // faster.
+ mFlyoutTransitionSpring.getSpring().setStiffness(
+ (mBubbleToExpandAfterFlyoutCollapse != null)
+ ? SpringForce.STIFFNESS_MEDIUM
+ : SpringForce.STIFFNESS_LOW);
mFlyoutTransitionSpring
.setStartValue(mFlyoutDragDeltaX)
.setStartVelocity(velX)
@@ -1295,123 +1343,121 @@ public class BubbleStackView extends FrameLayout {
: 0f);
}
- /**
- * Calculates how large the expanded view of the bubble can be. This takes into account the
- * y position when the bubbles are expanded as well as the bounds of the dismiss target.
- */
- int getMaxExpandedHeight() {
- int expandedY = (int) mExpandedAnimationController.getExpandedY();
- // PIP dismiss view uses FLAG_LAYOUT_IN_SCREEN so we need to subtract the bottom inset
- int pipDismissHeight = mPipDismissHeight - getBottomInset();
- return mDisplaySize.y - expandedY - mBubbleSize - pipDismissHeight;
+ /** Updates the dot visibility, this is used in response to a zen mode config change. */
+ void updateDots() {
+ int bubbsCount = mBubbleContainer.getChildCount();
+ for (int i = 0; i < bubbsCount; i++) {
+ BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+ // If nothing changed the animation won't happen
+ bv.updateDotVisibility(true /* animate */);
+ }
}
/**
* Calculates the y position of the expanded view when it is expanded.
*/
- float getYPositionForExpandedView() {
- return getStatusBarHeight() + mBubbleSize + mBubblePadding + mPointerHeight;
+ float getExpandedViewY() {
+ return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop + mPointerHeight;
}
/**
- * Called when the height of the currently expanded view has changed (not via an
- * update to the bubble's desired height but for some other reason, e.g. permission view
- * goes away).
+ * Animates in the flyout for the given bubble, if available, and then hides it after some time.
*/
- void onExpandedHeightChanged() {
- if (mIsExpanded) {
- requestUpdate();
- }
- }
+ @VisibleForTesting
+ void animateInFlyoutForBubble(Bubble bubble) {
+ final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
- /** Sets whether all bubbles in the stack should not show the 'new' dot. */
- void setSuppressNewDot(boolean suppressNewDot) {
- mSuppressNewDot = suppressNewDot;
+ if (!bubble.showFlyoutForBubble()) {
+ // In case flyout was suppressed for this update, reset now.
+ bubble.setSuppressFlyout(false);
+ return;
+ }
- for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
- BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
- bv.setSuppressDot(suppressNewDot, true /* animate */);
+ if (updateMessage == null
+ || isExpanded()
+ || mIsExpansionAnimating
+ || mIsGestureInProgress
+ || mBubbleToExpandAfterFlyoutCollapse != null) {
+ // Skip the message if none exists, we're expanded or animating expansion, or we're
+ // about to expand a bubble from the previous tapped flyout.
+ return;
}
- }
- /**
- * Sets whether the flyout should not appear, even if the notif otherwise would generate one.
- */
- void setSuppressFlyout(boolean suppressFlyout) {
- mSuppressFlyout = suppressFlyout;
- }
+ if (bubble.getIconView() != null) {
+ // Temporarily suppress the dot while the flyout is visible.
+ bubble.getIconView().setSuppressDot(
+ true /* suppressDot */, false /* animate */);
- /**
- * Callback to run after the flyout hides. Also called if a new flyout is shown before the
- * previous one animates out.
- */
- private Runnable mAfterFlyoutHides;
+ mFlyout.removeCallbacks(mAnimateInFlyout);
+ mFlyoutDragDeltaX = 0f;
- /**
- * Animates in the flyout for the given bubble, if available, and then hides it after some time.
- */
- @VisibleForTesting
- void animateInFlyoutForBubble(Bubble bubble) {
- final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
-
- // Show the message if one exists, and we're not expanded or animating expansion.
- if (updateMessage != null
- && !isExpanded()
- && !mIsExpansionAnimating
- && !mIsGestureInProgress
- && !mSuppressFlyout) {
- if (bubble.iconView != null) {
- // Temporarily suppress the dot while the flyout is visible.
- bubble.iconView.setSuppressDot(
- true /* suppressDot */, false /* animate */);
-
- mFlyoutDragDeltaX = 0f;
- mFlyout.setAlpha(0f);
-
- if (mAfterFlyoutHides != null) {
- mAfterFlyoutHides.run();
+ if (mAfterFlyoutHides != null) {
+ mAfterFlyoutHides.run();
+ }
+
+ mAfterFlyoutHides = () -> {
+ final boolean suppressDot = !bubble.showBubbleDot();
+ // If we're going to suppress the dot, make it visible first so it'll
+ // visibly animate away.
+ if (suppressDot) {
+ bubble.getIconView().setSuppressDot(
+ false /* suppressDot */, false /* animate */);
}
+ // Reset dot suppression. If we're not suppressing due to DND, then
+ // stop suppressing it with no animation (since the flyout has
+ // transformed into the dot). If we are suppressing due to DND, animate
+ // it away.
+ bubble.getIconView().setSuppressDot(
+ suppressDot /* suppressDot */,
+ suppressDot /* animate */);
+
+ if (mBubbleToExpandAfterFlyoutCollapse != null) {
+ mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
+ mBubbleData.setExpanded(true);
+ mBubbleToExpandAfterFlyoutCollapse = null;
+ }
+ };
- mAfterFlyoutHides = () -> {
- if (bubble.iconView == null) {
- return;
- }
+ mFlyout.setVisibility(INVISIBLE);
- // If we're going to suppress the dot, make it visible first so it'll
- // visibly animate away.
- if (mSuppressNewDot) {
- bubble.iconView.setSuppressDot(
- false /* suppressDot */, false /* animate */);
- }
+ // Post in case layout isn't complete and getWidth returns 0.
+ post(() -> {
+ // An auto-expanding bubble could have been posted during the time it takes to
+ // layout.
+ if (isExpanded()) {
+ return;
+ }
- // Reset dot suppression. If we're not suppressing due to DND, then
- // stop suppressing it with no animation (since the flyout has
- // transformed into the dot). If we are suppressing due to DND, animate
- // it away.
- bubble.iconView.setSuppressDot(
- mSuppressNewDot /* suppressDot */,
- mSuppressNewDot /* animate */);
+ final Runnable afterShow = () -> {
+ mAnimateInFlyout = () -> {
+ mFlyout.setVisibility(VISIBLE);
+ bubble.getIconView().setSuppressDot(
+ true /* suppressDot */, false /* animate */);
+ mFlyoutDragDeltaX =
+ mStackAnimationController.isStackOnLeftSide()
+ ? -mFlyout.getWidth()
+ : mFlyout.getWidth();
+ animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
+ mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+ };
+
+ mFlyout.postDelayed(mAnimateInFlyout, 200);
};
- // Post in case layout isn't complete and getWidth returns 0.
- post(() -> {
- // An auto-expanding bubble could have been posted during the time it takes to
- // layout.
- if (isExpanded()) {
- return;
- }
-
- mFlyout.showFlyout(
- updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
- mStackAnimationController.isStackOnLeftSide(),
- bubble.iconView.getBadgeColor(), mAfterFlyoutHides);
- });
- }
-
- mFlyout.removeCallbacks(mHideFlyout);
- mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+ mFlyout.setupFlyoutStartingAsDot(
+ updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+ mStackAnimationController.isStackOnLeftSide(),
+ bubble.getIconView().getBadgeColor(),
+ afterShow,
+ mAfterFlyoutHides,
+ bubble.getIconView().getDotCenter());
+ mFlyout.bringToFront();
+ });
}
+
+ mFlyout.removeCallbacks(mHideFlyout);
+ mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+ logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
}
/** Hide the flyout immediately and cancel any pending hide runnables. */
@@ -1420,6 +1466,7 @@ public class BubbleStackView extends FrameLayout {
mAfterFlyoutHides.run();
}
+ mFlyout.removeCallbacks(mAnimateInFlyout);
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.hideFlyout();
}
@@ -1430,6 +1477,11 @@ public class BubbleStackView extends FrameLayout {
if (mBubbleContainer.getChildCount() > 0) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
}
+ // Increase the touch target size of the bubble
+ outRect.top -= mBubbleTouchPadding;
+ outRect.left -= mBubbleTouchPadding;
+ outRect.right += mBubbleTouchPadding;
+ outRect.bottom += mBubbleTouchPadding;
} else {
mBubbleContainer.getBoundsOnScreen(outRect);
}
@@ -1454,14 +1506,6 @@ public class BubbleStackView extends FrameLayout {
return 0;
}
- private int getBottomInset() {
- if (getRootWindowInsets() != null) {
- WindowInsets insets = getRootWindowInsets();
- return insets.getSystemWindowInsetBottom();
- }
- return 0;
- }
-
private boolean isIntersecting(View view, float x, float y) {
mTempLoc = view.getLocationOnScreen();
mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
@@ -1479,33 +1523,33 @@ public class BubbleStackView extends FrameLayout {
}
private void updateExpandedBubble() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
}
mExpandedViewContainer.removeAllViews();
if (mExpandedBubble != null && mIsExpanded) {
- mExpandedViewContainer.addView(mExpandedBubble.expandedView);
- mExpandedBubble.expandedView.populateExpandedView();
+ mExpandedViewContainer.addView(mExpandedBubble.getExpandedView());
+ mExpandedBubble.getExpandedView().populateExpandedView();
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
mExpandedViewContainer.setAlpha(1.0f);
}
}
- private void applyCurrentState() {
- if (DEBUG) {
- Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+ private void updateExpandedView() {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
+ Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
}
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (mIsExpanded) {
// First update the view so that it calculates a new height (ensuring the y position
// calculation is correct)
- mExpandedBubble.expandedView.updateView();
- final float y = getYPositionForExpandedView();
+ mExpandedBubble.getExpandedView().updateView();
+ final float y = getExpandedViewY();
if (!mExpandedViewYAnim.isRunning()) {
// We're not animating so set the value
mExpandedViewContainer.setTranslationY(y);
- mExpandedBubble.expandedView.updateView();
+ mExpandedBubble.getExpandedView().updateView();
} else {
// We are animating so update the value; there is an end listener on the animator
// that will ensure expandedeView.updateView gets called.
@@ -1514,29 +1558,17 @@ public class BubbleStackView extends FrameLayout {
}
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
- updateBubbleShadowsAndDotPosition(false);
+ updateBubbleZOrdersAndDotPosition(false);
}
/** Sets the appropriate Z-order and dot position for each bubble in the stack. */
- private void updateBubbleShadowsAndDotPosition(boolean animate) {
- int bubbsCount = mBubbleContainer.getChildCount();
- for (int i = 0; i < bubbsCount; i++) {
+ private void updateBubbleZOrdersAndDotPosition(boolean animate) {
+ int bubbleCount = mBubbleContainer.getChildCount();
+ for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
bv.updateDotVisibility(true /* animate */);
bv.setZ((BubbleController.MAX_BUBBLES
* getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
-
- // Draw the shadow around the circle inscribed within the bubble's bounds. This
- // (intentionally) does not draw a shadow behind the update dot, which should be drawing
- // its own shadow since it's on a different (higher) plane.
- bv.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setOval(0, 0, mBubbleSize, mBubbleSize);
- }
- });
- bv.setClipToOutline(false);
-
// If the dot is on the left, and so is the stack, we need to change the dot position.
if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
@@ -1545,7 +1577,7 @@ public class BubbleStackView extends FrameLayout {
}
private void updatePointerPosition() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updatePointerPosition()");
}
@@ -1563,7 +1595,7 @@ public class BubbleStackView extends FrameLayout {
// Remove padding when deriving pointer location from bubbles.
float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding;
- expandedBubble.expandedView.setPointerPosition(bubbleCenter);
+ expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
}
/**
@@ -1584,7 +1616,7 @@ public class BubbleStackView extends FrameLayout {
if (bubble == null) {
return 0;
}
- return mBubbleContainer.indexOfChild(bubble.iconView);
+ return mBubbleContainer.indexOfChild(bubble.getIconView());
}
/**
@@ -1617,8 +1649,8 @@ public class BubbleStackView extends FrameLayout {
* @param action the user interaction enum.
*/
private void logBubbleEvent(@Nullable Bubble bubble, int action) {
- if (bubble == null || bubble.entry == null
- || bubble.entry.notification == null) {
+ if (bubble == null || bubble.getEntry() == null
+ || bubble.getEntry().notification == null) {
StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
null /* package name */,
null /* notification channel */,
@@ -1630,9 +1662,9 @@ public class BubbleStackView extends FrameLayout {
getNormalizedYPosition(),
false /* unread bubble */,
false /* on-going bubble */,
- false /* foreground bubble */);
+ false /* isAppForeground (unused) */);
} else {
- StatusBarNotification notification = bubble.entry.notification;
+ StatusBarNotification notification = bubble.getEntry().notification;
StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
notification.getPackageName(),
notification.getNotification().getChannelId(),
@@ -1642,9 +1674,9 @@ public class BubbleStackView extends FrameLayout {
action,
getNormalizedXPosition(),
getNormalizedYPosition(),
- bubble.entry.showInShadeWhenBubble(),
- bubble.entry.isForegroundService(),
- BubbleController.isForegroundApp(mContext, notification.getPackageName()));
+ bubble.showInShadeWhenBubble(),
+ bubble.isOngoing(),
+ false /* isAppForeground (unused) */);
}
}
@@ -1656,7 +1688,7 @@ public class BubbleStackView extends FrameLayout {
if (!isExpanded()) {
return false;
}
- return mExpandedBubble.expandedView.performBackPressIfNeeded();
+ return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
}
/** For debugging only */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 8fe8bd305707..4240e06a8800 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -18,7 +18,6 @@ package com.android.systemui.bubbles;
import android.content.Context;
import android.graphics.PointF;
-import android.os.Handler;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -45,7 +44,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
*/
private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
- private static final String TAG = "BubbleTouchHandler";
/**
* When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
* towards the center of the screen (where the dismiss target is). This value is the width of
@@ -66,11 +64,10 @@ class BubbleTouchHandler implements View.OnTouchListener {
private int mTouchSlopSquared;
private VelocityTracker mVelocityTracker;
- private boolean mInDismissTarget;
- private Handler mHandler = new Handler();
-
/** View that was initially touched, when we received the first ACTION_DOWN event. */
private View mTouchedView;
+ /** Whether the current touched view is in the dismiss target. */
+ private boolean mInDismissTarget;
BubbleTouchHandler(BubbleStackView stackView,
BubbleData bubbleData, Context context) {
@@ -98,6 +95,15 @@ class BubbleTouchHandler implements View.OnTouchListener {
return false;
}
+ if (!(mTouchedView instanceof BubbleView)
+ && !(mTouchedView instanceof BubbleStackView)
+ && !(mTouchedView instanceof BubbleFlyoutView)) {
+ // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
+ // of expanded view).
+ resetForNextGesture();
+ return false;
+ }
+
final boolean isStack = mStack.equals(mTouchedView);
final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
final float rawX = event.getRawX();
@@ -193,9 +199,8 @@ class BubbleTouchHandler implements View.OnTouchListener {
}
});
} else if (isFlyout) {
- // TODO(b/129768381): Expand if tapped, dismiss if swiped away.
if (!mBubbleData.isExpanded() && !mMovedEnough) {
- mBubbleData.setExpanded(true);
+ mStack.onFlyoutTapped();
}
} else if (mMovedEnough) {
if (isStack) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6f1ed28d649e..603c4169c169 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -19,16 +19,23 @@ package com.android.systemui.bubbles;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.InsetDrawable;
import android.util.AttributeSet;
+import android.util.PathParser;
import android.widget.FrameLayout;
import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.ShadowGenerator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,23 +45,25 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
* A floating object on the screen that can post message updates.
*/
public class BubbleView extends FrameLayout {
- private static final String TAG = "BubbleView";
private static final int DARK_ICON_ALPHA = 180;
private static final double ICON_MIN_CONTRAST = 4.1;
- private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
+ private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
// Same value as Launcher3 badge code
private static final float WHITE_SCRIM_ALPHA = 0.54f;
private Context mContext;
private BadgedImageView mBadgedImageView;
private int mBadgeColor;
- private int mPadding;
private int mIconInset;
+ private Drawable mUserBadgedAppIcon;
+
+ // mBubbleIconFactory cannot be static because it depends on Context.
+ private BubbleIconFactory mBubbleIconFactory;
private boolean mSuppressDot = false;
- private NotificationEntry mEntry;
+ private Bubble mBubble;
public BubbleView(Context context) {
this(context, null);
@@ -71,8 +80,6 @@ public class BubbleView extends FrameLayout {
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
- // XXX: can this padding just be on the view and we look it up?
- mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
}
@@ -88,16 +95,15 @@ public class BubbleView extends FrameLayout {
}
/**
- * Populates this view with a notification.
+ * Populates this view with a bubble.
* <p>
- * This should only be called when a new notification is being set on the view, updates to the
- * current notification should use {@link #update(NotificationEntry)}.
+ * This should only be called when a new bubble is being set on the view, updates to the
+ * current bubble should use {@link #update(Bubble)}.
*
- * @param entry the notification to display as a bubble.
+ * @param bubble the bubble to display in this view.
*/
- public void setNotif(NotificationEntry entry) {
- mEntry = entry;
- updateViews();
+ public void setBubble(Bubble bubble) {
+ mBubble = bubble;
}
/**
@@ -105,7 +111,7 @@ public class BubbleView extends FrameLayout {
*/
@Nullable
public NotificationEntry getEntry() {
- return mEntry;
+ return mBubble != null ? mBubble.getEntry() : null;
}
/**
@@ -113,24 +119,34 @@ public class BubbleView extends FrameLayout {
*/
@Nullable
public String getKey() {
- return (mEntry != null) ? mEntry.key : null;
+ return (mBubble != null) ? mBubble.getKey() : null;
}
/**
- * Updates the UI based on the entry, updates badge and animates messages as needed.
+ * Updates the UI based on the bubble, updates badge and animates messages as needed.
*/
- public void update(NotificationEntry entry) {
- mEntry = entry;
+ public void update(Bubble bubble) {
+ mBubble = bubble;
updateViews();
}
/**
+ * @param factory Factory for creating normalized bubble icons.
+ */
+ public void setBubbleIconFactory(BubbleIconFactory factory) {
+ mBubbleIconFactory = factory;
+ }
+
+ public void setAppIcon(Drawable appIcon) {
+ mUserBadgedAppIcon = appIcon;
+ }
+ /**
* @return the {@link ExpandableNotificationRow} view to display notification content when the
* bubble is expanded.
*/
@Nullable
public ExpandableNotificationRow getRowView() {
- return (mEntry != null) ? mEntry.getRow() : null;
+ return (mBubble != null) ? mBubble.getEntry().getRow() : null;
}
/** Changes the dot's visibility to match the bubble view's state. */
@@ -150,18 +166,23 @@ public class BubbleView extends FrameLayout {
/** Sets the position of the 'new' dot, animating it out and back in if requested. */
void setDotPosition(boolean onLeft, boolean animate) {
- if (animate && onLeft != mBadgedImageView.getDotPosition() && !mSuppressDot) {
+ if (animate && onLeft != mBadgedImageView.getDotOnLeft() && !mSuppressDot) {
animateDot(false /* showDot */, () -> {
- mBadgedImageView.setDotPosition(onLeft);
+ mBadgedImageView.setDotOnLeft(onLeft);
animateDot(true /* showDot */, null);
});
} else {
- mBadgedImageView.setDotPosition(onLeft);
+ mBadgedImageView.setDotOnLeft(onLeft);
}
}
+ float[] getDotCenter() {
+ float[] unscaled = mBadgedImageView.getDotCenter();
+ return new float[]{unscaled[0], unscaled[1]};
+ }
+
boolean getDotPositionOnLeft() {
- return mBadgedImageView.getDotPosition();
+ return mBadgedImageView.getDotOnLeft();
}
/**
@@ -169,7 +190,7 @@ public class BubbleView extends FrameLayout {
* after animation if requested.
*/
private void updateDotVisibility(boolean animate, Runnable after) {
- boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
+ boolean showDot = mBubble.showBubbleDot() && !mSuppressDot;
if (animate) {
animateDot(showDot, after);
@@ -186,7 +207,6 @@ public class BubbleView extends FrameLayout {
if (showDot) {
mBadgedImageView.setShowDot(true);
}
-
mBadgedImageView.clearAnimation();
mBadgedImageView.animate().setDuration(200)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
@@ -207,37 +227,60 @@ public class BubbleView extends FrameLayout {
}
void updateViews() {
- if (mEntry == null) {
+ if (mBubble == null || mBubbleIconFactory == null) {
return;
}
- Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
- Notification n = mEntry.notification.getNotification();
- Icon ic;
- boolean needsTint;
- if (metadata != null) {
- ic = metadata.getIcon();
- needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
- } else {
- needsTint = n.getLargeIcon() == null;
- ic = needsTint ? n.getSmallIcon() : n.getLargeIcon();
- }
+ // Update icon.
+ Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
+ Notification n = mBubble.getEntry().notification.getNotification();
+ Icon ic = metadata.getIcon();
+ boolean needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
+
Drawable iconDrawable = ic.loadDrawable(mContext);
if (needsTint) {
- mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
- } else {
- mBadgedImageView.setImageDrawable(iconDrawable);
+ iconDrawable = buildIconWithTint(iconDrawable, n.color);
}
+ Bitmap bubbleIcon = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
+ null /* user */,
+ true /* shrinkNonAdaptiveIcons */).icon;
+
+ // Give it a shadow
+ Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap(mUserBadgedAppIcon,
+ 1f, mBubbleIconFactory.getBadgeSize());
+ Canvas c = new Canvas();
+ ShadowGenerator shadowGenerator = new ShadowGenerator(mBubbleIconFactory.getBadgeSize());
+ c.setBitmap(userBadgedBitmap);
+ shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
+
+ mBubbleIconFactory.badgeWithDrawable(bubbleIcon,
+ new BitmapDrawable(mContext.getResources(), userBadgedBitmap));
+ mBadgedImageView.setImageBitmap(bubbleIcon);
+
+ // Update badge.
int badgeColor = determineDominateColor(iconDrawable, n.color);
mBadgeColor = badgeColor;
mBadgedImageView.setDotColor(badgeColor);
- animateDot(mEntry.showInShadeWhenBubble() /* showDot */, null /* after */);
+
+ // Update dot.
+ Path iconPath = PathParser.createPathFromPathData(
+ getResources().getString(com.android.internal.R.string.config_icon_mask));
+ Matrix matrix = new Matrix();
+ float scale = mBubbleIconFactory.getNormalizer().getScale(iconDrawable,
+ null /* outBounds */, null /* path */, null /* outMaskShape */);
+ float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
+ matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+ radius /* pivot y */);
+ iconPath.transform(matrix);
+ mBadgedImageView.drawDot(iconPath);
+
+ animateDot(mBubble.showBubbleDot() /* showDot */, null /* after */);
}
int getBadgeColor() {
return mBadgeColor;
}
- private Drawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
+ private AdaptiveIconDrawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
iconDrawable = checkTint(iconDrawable, backgroundColor);
InsetDrawable foreground = new InsetDrawable(iconDrawable, mIconInset);
ColorDrawable background = new ColorDrawable(backgroundColor);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 1fa0e12452e1..c332d15a9b47 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -16,7 +16,9 @@
package com.android.systemui.bubbles.animation;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.view.View;
@@ -26,10 +28,13 @@ import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.google.android.collect.Sets;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Set;
/**
@@ -46,24 +51,26 @@ public class ExpandedAnimationController
*/
private static final int ANIMATE_TRANSLATION_FACTOR = 4;
- /** How much to scale down bubbles when they're animating in/out. */
- private static final float ANIMATE_SCALE_PERCENT = 0.5f;
+ /** Duration of the expand/collapse target path animation. */
+ private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
- /** The stack position to collapse back to in {@link #collapseBackToStack}. */
- private PointF mCollapseToPoint;
+ /** Stiffness for the expand/collapse path-following animation. */
+ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
/** 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;
+ /** Space between status bar and bubbles in the expanded state. */
+ private float mBubblePaddingTop;
/** Size of each bubble. */
private float mBubbleSizePx;
/** Height of the status bar. */
private float mStatusBarHeight;
/** Size of display. */
private Point mDisplaySize;
- /** Size of dismiss target at bottom of screen. */
- private float mPipDismissHeight;
+ /** Max number of bubbles shown in row above expanded view.*/
+ private int mBubblesMaxRendered;
+ /** Width of current screen orientation. */
+ private float mScreenWidth;
/** Whether the dragged-out bubble is in the dismiss target. */
private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -86,10 +93,14 @@ public class ExpandedAnimationController
private boolean mSpringingBubbleToTouch = false;
private int mExpandedViewPadding;
+ private float mLauncherGridDiff;
- public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
+ public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
+ int orientation) {
mDisplaySize = displaySize;
+ updateOrientation(orientation);
mExpandedViewPadding = expandedViewPadding;
+ mLauncherGridDiff = 30f;
}
/**
@@ -109,7 +120,7 @@ public class ExpandedAnimationController
mAnimatingExpand = true;
mAfterExpand = after;
- startOrUpdateExpandAnimation();
+ startOrUpdatePathAnimation(true /* expanding */);
}
/** Animate collapsing the bubbles back to their stacked position. */
@@ -119,43 +130,107 @@ public class ExpandedAnimationController
mAfterCollapse = after;
mCollapsePoint = collapsePoint;
- startOrUpdateCollapseAnimation();
+ startOrUpdatePathAnimation(false /* expanding */);
}
- private void startOrUpdateExpandAnimation() {
- animationsForChildrenFromIndex(
- 0, /* startIndex */
- (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
- .startAll(() -> {
- mAnimatingExpand = false;
+ /**
+ * Update effective screen width based on current orientation.
+ * @param orientation Landscape or portrait.
+ */
+ public void updateOrientation(int orientation) {
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ mScreenWidth = mDisplaySize.y;
+ } else {
+ mScreenWidth = mDisplaySize.x;
+ }
+ if (mLayout != null) {
+ Resources res = mLayout.getContext().getResources();
+ mStatusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ }
+ }
- if (mAfterExpand != null) {
- mAfterExpand.run();
- }
+ /**
+ * Animates the bubbles along a curved path, either to expand them along the top or collapse
+ * them back into a stack.
+ */
+ private void startOrUpdatePathAnimation(boolean expanding) {
+ Runnable after;
- mAfterExpand = null;
- });
- }
+ if (expanding) {
+ after = () -> {
+ mAnimatingExpand = false;
- private void startOrUpdateCollapseAnimation() {
- // Stack to the left if we're going to the left, or right if not.
- final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
- animationsForChildrenFromIndex(
- 0, /* startIndex */
- (index, animation) -> {
- animation.position(
- mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
- mCollapsePoint.y);
- })
- .startAll(() -> {
- mAnimatingCollapse = false;
-
- if (mAfterCollapse != null) {
- mAfterCollapse.run();
- }
-
- mAfterCollapse = null;
- });
+ if (mAfterExpand != null) {
+ mAfterExpand.run();
+ }
+
+ mAfterExpand = null;
+ };
+ } else {
+ after = () -> {
+ mAnimatingCollapse = false;
+
+ if (mAfterCollapse != null) {
+ mAfterCollapse.run();
+ }
+
+ mAfterCollapse = null;
+ };
+ }
+
+ // Animate each bubble individually, since each path will end in a different spot.
+ animationsForChildrenFromIndex(0, (index, animation) -> {
+ final View bubble = mLayout.getChildAt(index);
+
+ // Start a path at the bubble's current position.
+ final Path path = new Path();
+ path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
+
+ final float expandedY = getExpandedY();
+ if (expanding) {
+ // If we're expanding, first draw a line from the bubble's current position to the
+ // top of the screen.
+ path.lineTo(bubble.getTranslationX(), expandedY);
+
+ // Then, draw a line across the screen to the bubble's resting position.
+ path.lineTo(getBubbleLeft(index), expandedY);
+ } else {
+ final float sideMultiplier =
+ mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
+ final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+
+ // If we're collapsing, draw a line from the bubble's current position to the side
+ // of the screen where the bubble will be stacked.
+ path.lineTo(stackedX, expandedY);
+
+ // Then, draw a line down to the stack position.
+ path.lineTo(stackedX, mCollapsePoint.y);
+ }
+
+ // The lead bubble should be the bubble with the longest distance to travel when we're
+ // expanding, and the bubble with the shortest distance to travel when we're collapsing.
+ // During expansion from the left side, the last bubble has to travel to the far right
+ // side, so we have it lead and 'pull' the rest of the bubbles into place. From the
+ // right side, the first bubble is traveling to the top left, so it leads. During
+ // collapse to the left, the first bubble has the shortest travel time back to the stack
+ // position, so it leads (and vice versa).
+ final boolean firstBubbleLeads =
+ (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+ || (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+ final int startDelay = firstBubbleLeads
+ ? (index * 10)
+ : ((mLayout.getChildCount() - index) * 10);
+
+ animation
+ .followAnimatedTargetAlongPath(
+ path,
+ EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
+ Interpolators.LINEAR /* targetAnimInterpolator */)
+ .withStartDelay(startDelay)
+ .withStiffness(EXPAND_COLLAPSE_ANIM_STIFFNESS);
+ }).startAll(after);
}
/** Prepares the given bubble to be dragged out. */
@@ -265,6 +340,7 @@ public class ExpandedAnimationController
public void onGestureFinished() {
mBubbleDraggedOutEnough = false;
mBubbleDraggingOut = null;
+ updateBubblePositions();
}
/**
@@ -276,41 +352,38 @@ public class ExpandedAnimationController
0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
}
- /**
- * Animates the bubbles, starting at the given index, to the left or right by the given number
- * of bubble widths. Passing zero for numBubbleWidths will animate the bubbles to their normal
- * positions.
- */
- private void animateStackByBubbleWidthsStartingFrom(int numBubbleWidths, int startIndex) {
- animationsForChildrenFromIndex(
- startIndex,
- (index, animation) ->
- animation.translationX(getXForChildAtIndex(index + numBubbleWidths)))
- .startAll();
- }
-
/** The Y value of the row of expanded bubbles. */
public float getExpandedY() {
if (mLayout == null || mLayout.getRootWindowInsets() == null) {
return 0;
}
final WindowInsets insets = mLayout.getRootWindowInsets();
- return mBubblePaddingPx + Math.max(
+ return mBubblePaddingTop + Math.max(
mStatusBarHeight,
insets.getDisplayCutout() != null
? insets.getDisplayCutout().getSafeInsetTop()
: 0);
}
+ /** Description of current animation controller state. */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("ExpandedAnimationController state:");
+ pw.print(" isActive: "); pw.println(isActiveController());
+ pw.print(" animatingExpand: "); pw.println(mAnimatingExpand);
+ pw.print(" animatingCollapse: "); pw.println(mAnimatingCollapse);
+ pw.print(" bubbleInDismiss: "); pw.println(mIndividualBubbleWithinDismissTarget);
+ pw.print(" springingBubble: "); pw.println(mSpringingBubbleToTouch);
+ }
+
@Override
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
final Resources res = layout.getResources();
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+ mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
// Ensure that all child views are at 1x scale, and visible, in case they were animating
// in.
@@ -351,11 +424,11 @@ public class ExpandedAnimationController
// If a bubble is added while the expand/collapse animations are playing, update the
// animation to include the new bubble.
if (mAnimatingExpand) {
- startOrUpdateExpandAnimation();
+ startOrUpdatePathAnimation(true /* expanding */);
} else if (mAnimatingCollapse) {
- startOrUpdateCollapseAnimation();
+ startOrUpdatePathAnimation(false /* expanding */);
} else {
- child.setTranslationX(getXForChildAtIndex(index));
+ child.setTranslationX(getBubbleLeft(index));
animationForChild(child)
.translationY(
getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
@@ -389,6 +462,12 @@ public class ExpandedAnimationController
@Override
void onChildReordered(View child, int oldIndex, int newIndex) {
updateBubblePositions();
+
+ // We expect reordering during collapse, since we'll put the last selected bubble on top.
+ // Update the collapse animation so they end up in the right stacked positions.
+ if (mAnimatingCollapse) {
+ startOrUpdatePathAnimation(false /* expanding */);
+ }
}
private void updateBubblePositions() {
@@ -411,35 +490,53 @@ public class ExpandedAnimationController
}
}
- /** Returns the appropriate X translation value for a bubble at the given index. */
- private float getXForChildAtIndex(int index) {
- return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
- }
-
/**
* @param index Bubble index in row.
* @return Bubble left x from left edge of screen.
*/
public float getBubbleLeft(int index) {
- float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
- return getRowLeft() + bubbleLeftFromRowLeft;
+ final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles());
+ return getRowLeft() + bubbleFromRowLeft;
}
private float getRowLeft() {
if (mLayout == null) {
return 0;
}
+
int bubbleCount = mLayout.getChildCount();
- // Width calculations.
- double bubble = bubbleCount * mBubbleSizePx;
- float gap = (bubbleCount - 1) * mBubblePaddingPx;
- float row = gap + (float) bubble;
+ final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
+ final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+ final float rowWidth = totalGapWidth + totalBubbleWidth;
+
+ final float centerScreen = mScreenWidth / 2f;
+ final float halfRow = rowWidth / 2f;
+ final float rowLeft = centerScreen - halfRow;
- float halfRow = row / 2f;
- float centerScreen = mDisplaySize.x / 2;
- float rowLeftFromScreenLeft = centerScreen - halfRow;
+ return rowLeft;
+ }
- return rowLeftFromScreenLeft;
+ /**
+ * @return Space between bubbles in row above expanded view.
+ */
+ private float getSpaceBetweenBubbles() {
+ /**
+ * Ordered left to right:
+ * Screen edge
+ * [mExpandedViewPadding]
+ * Expanded view edge
+ * [launcherGridDiff] --- arbitrary value until launcher exports widths
+ * Launcher's app icon grid edge that we must match
+ */
+ final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+ final float maxRowWidth = mScreenWidth - rowMargins;
+
+ final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
+ final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+ final int gapCount = mBubblesMaxRendered - 1;
+ final float gapWidth = totalGapWidth / gapCount;
+ return gapWidth;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 3a3339249d5b..563a0a7e43e1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -16,7 +16,14 @@
package com.android.systemui.bubbles.animation;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.content.Context;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatProperty;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -160,7 +167,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Whether this controller is the currently active controller for its associated layout. */
protected boolean isActiveController() {
- return this == mLayout.mController;
+ return mLayout != null && this == mLayout.mController;
}
protected void setLayout(PhysicsAnimationLayout layout) {
@@ -232,7 +239,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
}
if (endActions != null) {
- mLayout.setEndActionForMultipleProperties(
+ setEndActionForMultipleProperties(
runAllEndActions,
allAnimatedProperties.toArray(
new DynamicAnimation.ViewProperty[0]));
@@ -243,6 +250,44 @@ public class PhysicsAnimationLayout extends FrameLayout {
}
};
}
+
+ /**
+ * Sets an end action that will be run when all child animations for a given property have
+ * stopped running.
+ */
+ protected void setEndActionForProperty(
+ Runnable action, DynamicAnimation.ViewProperty property) {
+ mLayout.mEndActionForProperty.put(property, action);
+ }
+
+ /**
+ * Sets an end action that will be run when all child animations for all of the given
+ * properties have stopped running.
+ */
+ protected void setEndActionForMultipleProperties(
+ Runnable action, DynamicAnimation.ViewProperty... properties) {
+ final Runnable checkIfAllFinished = () -> {
+ if (!mLayout.arePropertiesAnimating(properties)) {
+ action.run();
+
+ for (DynamicAnimation.ViewProperty property : properties) {
+ removeEndActionForProperty(property);
+ }
+ }
+ };
+
+ for (DynamicAnimation.ViewProperty property : properties) {
+ setEndActionForProperty(checkIfAllFinished, property);
+ }
+ }
+
+ /**
+ * Removes the end listener that would have been called when all child animations for a
+ * given property stopped running.
+ */
+ protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
+ mLayout.mEndActionForProperty.remove(property);
+ }
}
/**
@@ -275,43 +320,6 @@ public class PhysicsAnimationLayout extends FrameLayout {
}
}
- /**
- * Sets an end action that will be run when all child animations for a given property have
- * stopped running.
- */
- public void setEndActionForProperty(Runnable action, DynamicAnimation.ViewProperty property) {
- mEndActionForProperty.put(property, action);
- }
-
- /**
- * Sets an end action that will be run when all child animations for all of the given properties
- * have stopped running.
- */
- public void setEndActionForMultipleProperties(
- Runnable action, DynamicAnimation.ViewProperty... properties) {
- final Runnable checkIfAllFinished = () -> {
- if (!arePropertiesAnimating(properties)) {
- action.run();
-
- for (DynamicAnimation.ViewProperty property : properties) {
- removeEndActionForProperty(property);
- }
- }
- };
-
- for (DynamicAnimation.ViewProperty property : properties) {
- setEndActionForProperty(checkIfAllFinished, property);
- }
- }
-
- /**
- * Removes the end listener that would have been called when all child animations for a given
- * property stopped running.
- */
- public void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
- mEndActionForProperty.remove(property);
- }
-
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
addViewInternal(child, index, params, false /* isReorder */);
@@ -372,11 +380,22 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Checks whether any animations of the given properties are running on the given view. */
public boolean arePropertiesAnimatingOnView(
View view, DynamicAnimation.ViewProperty... properties) {
+ final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
for (DynamicAnimation.ViewProperty property : properties) {
final SpringAnimation animation = getAnimationFromView(property, view);
if (animation != null && animation.isRunning()) {
return true;
}
+
+ // If the target animator is running, its update listener will trigger the translation
+ // physics animations at some point. We should consider the translation properties to be
+ // be animating in this case, even if the physics animations haven't been started yet.
+ final boolean isTranslation =
+ property.equals(DynamicAnimation.TRANSLATION_X)
+ || property.equals(DynamicAnimation.TRANSLATION_Y);
+ if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) {
+ return true;
+ }
}
return false;
@@ -388,8 +407,18 @@ public class PhysicsAnimationLayout extends FrameLayout {
return;
}
+ cancelAllAnimationsOfProperties(
+ mController.getAnimatedProperties().toArray(new DynamicAnimation.ViewProperty[]{}));
+ }
+
+ /** Cancels all animations that are running on all child views, for the given properties. */
+ public void cancelAllAnimationsOfProperties(DynamicAnimation.ViewProperty... properties) {
+ if (mController == null) {
+ return;
+ }
+
for (int i = 0; i < getChildCount(); i++) {
- for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+ for (DynamicAnimation.ViewProperty property : properties) {
final DynamicAnimation anim = getAnimationAtIndex(property, i);
if (anim != null) {
anim.cancel();
@@ -400,6 +429,14 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Cancels all of the physics animations running on the given view. */
public void cancelAnimationsOnView(View view) {
+ // If present, cancel the target animator so it doesn't restart the translation physics
+ // animations.
+ final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
+ if (targetAnimator != null) {
+ targetAnimator.cancel();
+ }
+
+ // Cancel physics animations on the view.
for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
getAnimationFromView(property, view).cancel();
}
@@ -470,6 +507,11 @@ public class PhysicsAnimationLayout extends FrameLayout {
return (SpringAnimation) view.getTag(getTagIdForProperty(property));
}
+ /** Retrieves the target animator from the view via the view tag system. */
+ @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) {
+ return (ObjectAnimator) view.getTag(R.id.target_animator_tag);
+ }
+
/** 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++) {
@@ -587,7 +629,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
* End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed,
* if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously.
*/
- private Runnable[] mPositionEndActions;
+ @Nullable private Runnable[] mPositionEndActions;
/**
* All of the properties that have been set and will animate when {@link #start} is called.
@@ -603,6 +645,46 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** The animation controller that last retrieved this animator instance. */
private PhysicsAnimationController mAssociatedController;
+ /**
+ * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As
+ * the path is traversed, the view's translation spring animation final positions are
+ * updated such that the view 'follows' the current position on the path.
+ */
+ @Nullable private ObjectAnimator mPathAnimator;
+
+ /** Current position on the path. This is animated by {@link #mPathAnimator}. */
+ private PointF mCurrentPointOnPath = new PointF();
+
+ /**
+ * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value
+ * of {@link #mCurrentPointOnPath}.
+ */
+ private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty =
+ new FloatProperty<PhysicsPropertyAnimator>("PathX") {
+ @Override
+ public void setValue(PhysicsPropertyAnimator object, float value) {
+ mCurrentPointOnPath.x = value;
+ }
+
+ @Override
+ public Float get(PhysicsPropertyAnimator object) {
+ return mCurrentPointOnPath.x;
+ }
+ };
+
+ private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty =
+ new FloatProperty<PhysicsPropertyAnimator>("PathY") {
+ @Override
+ public void setValue(PhysicsPropertyAnimator object, float value) {
+ mCurrentPointOnPath.y = value;
+ }
+
+ @Override
+ public Float get(PhysicsPropertyAnimator object) {
+ return mCurrentPointOnPath.y;
+ }
+ };
+
protected PhysicsPropertyAnimator(View view) {
this.mView = view;
}
@@ -628,6 +710,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Animate the view's translationX value to the provided value. */
public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) {
+ mPathAnimator = null; // We aren't using the path anymore if we're translating.
return property(DynamicAnimation.TRANSLATION_X, translationX, endActions);
}
@@ -640,6 +723,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Animate the view's translationY value to the provided value. */
public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) {
+ mPathAnimator = null; // We aren't using the path anymore if we're translating.
return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions);
}
@@ -661,6 +745,46 @@ public class PhysicsAnimationLayout extends FrameLayout {
return translationY(translationY);
}
+ /**
+ * Animates a 'target' point that moves along the given path, using the provided duration
+ * and interpolator to animate the target. The view itself is animated using physics-based
+ * animations, whose final positions are updated to the target position as it animates. This
+ * results in the view 'following' the target in a realistic way.
+ *
+ * This method will override earlier calls to {@link #translationX}, {@link #translationY},
+ * or {@link #position}, ultimately animating the view's position to the final point on the
+ * given path.
+ *
+ * Any provided end listeners will be called when the physics-based animations kicked off by
+ * the moving target have completed - not when the target animation completes.
+ */
+ public PhysicsPropertyAnimator followAnimatedTargetAlongPath(
+ Path path,
+ int targetAnimDuration,
+ TimeInterpolator targetAnimInterpolator,
+ Runnable... endActions) {
+ mPathAnimator = ObjectAnimator.ofFloat(
+ this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
+ mPathAnimator.setDuration(targetAnimDuration);
+ mPathAnimator.setInterpolator(targetAnimInterpolator);
+
+ mPositionEndActions = endActions;
+
+ // Remove translation related values since we're going to ignore them and follow the
+ // path instead.
+ clearTranslationValues();
+ return this;
+ }
+
+ private void clearTranslationValues() {
+ mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X);
+ mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y);
+ mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X);
+ mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y);
+ mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X);
+ mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y);
+ }
+
/** Animate the view's scaleX value to the provided value. */
public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) {
return property(DynamicAnimation.SCALE_X, scaleX, endActions);
@@ -742,7 +866,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
if (after != null && after.length > 0) {
final DynamicAnimation.ViewProperty[] propertiesArray =
properties.toArray(new DynamicAnimation.ViewProperty[0]);
- setEndActionForMultipleProperties(() -> {
+ mAssociatedController.setEndActionForMultipleProperties(() -> {
for (Runnable callback : after) {
callback.run();
}
@@ -774,8 +898,20 @@ public class PhysicsAnimationLayout extends FrameLayout {
new Runnable[]{waitForBothXAndY});
}
+ if (mPathAnimator != null) {
+ startPathAnimation();
+ }
+
// Actually start the animations.
for (DynamicAnimation.ViewProperty property : properties) {
+ // Don't start translation animations if we're using a path animator, the update
+ // listeners added to that animator will take care of that.
+ if (mPathAnimator != null
+ && (property.equals(DynamicAnimation.TRANSLATION_X)
+ || property.equals(DynamicAnimation.TRANSLATION_Y))) {
+ return;
+ }
+
if (mInitialPropertyValues.containsKey(property)) {
property.setValue(mView, mInitialPropertyValues.get(property));
}
@@ -797,7 +933,16 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Returns the set of properties that will animate once {@link #start} is called. */
protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
- return mAnimatedProperties.keySet();
+ final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>(
+ mAnimatedProperties.keySet());
+
+ // If we're using a path animator, it'll kick off translation animations.
+ if (mPathAnimator != null) {
+ animatedProperties.add(DynamicAnimation.TRANSLATION_X);
+ animatedProperties.add(DynamicAnimation.TRANSLATION_Y);
+ }
+
+ return animatedProperties;
}
/**
@@ -812,7 +957,7 @@ public class PhysicsAnimationLayout extends FrameLayout {
long startDelay,
float stiffness,
float dampingRatio,
- Runnable[] afterCallbacks) {
+ Runnable... afterCallbacks) {
if (view != null) {
final SpringAnimation animation =
(SpringAnimation) view.getTag(getTagIdForProperty(property));
@@ -855,6 +1000,92 @@ public class PhysicsAnimationLayout extends FrameLayout {
}
}
+ /**
+ * Updates the final position of a view's animation, without changing any of the animation's
+ * other settings. Calling this before an initial call to {@link #animateValueForChild} will
+ * work, but result in unknown values for stiffness, etc. and is not recommended.
+ */
+ private void updateValueForChild(
+ DynamicAnimation.ViewProperty property, View view, float position) {
+ if (view != null) {
+ final SpringAnimation animation =
+ (SpringAnimation) view.getTag(getTagIdForProperty(property));
+ final SpringForce animationSpring = animation.getSpring();
+
+ if (animationSpring == null) {
+ return;
+ }
+
+ animationSpring.setFinalPosition(position);
+ animation.start();
+ }
+ }
+
+ /**
+ * Configures the path animator to respect the settings passed into the animation builder
+ * and adds update listeners that update the translation physics animations. Then, starts
+ * the path animation.
+ */
+ protected void startPathAnimation() {
+ final SpringForce defaultSpringForceX = mController.getSpringForce(
+ DynamicAnimation.TRANSLATION_X, mView);
+ final SpringForce defaultSpringForceY = mController.getSpringForce(
+ DynamicAnimation.TRANSLATION_Y, mView);
+
+ if (mStartDelay > 0) {
+ mPathAnimator.setStartDelay(mStartDelay);
+ }
+
+ final Runnable updatePhysicsAnims = () -> {
+ updateValueForChild(
+ DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x);
+ updateValueForChild(
+ DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y);
+ };
+
+ mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run());
+ mPathAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ animateValueForChild(
+ DynamicAnimation.TRANSLATION_X,
+ mView,
+ mCurrentPointOnPath.x,
+ mDefaultStartVelocity,
+ 0 /* startDelay */,
+ mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(),
+ mDampingRatio >= 0
+ ? mDampingRatio
+ : defaultSpringForceX.getDampingRatio());
+
+ animateValueForChild(
+ DynamicAnimation.TRANSLATION_Y,
+ mView,
+ mCurrentPointOnPath.y,
+ mDefaultStartVelocity,
+ 0 /* startDelay */,
+ mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(),
+ mDampingRatio >= 0
+ ? mDampingRatio
+ : defaultSpringForceY.getDampingRatio());
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updatePhysicsAnims.run();
+ }
+ });
+
+ // If there's a target animator saved for the view, make sure it's not running.
+ final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView);
+ if (targetAnimator != null) {
+ targetAnimator.cancel();
+ }
+
+ mView.setTag(R.id.target_animator_tag, mPathAnimator);
+ mPathAnimator.start();
+ }
+
private void clearAnimator() {
mInitialPropertyValues.clear();
mAnimatedProperties.clear();
@@ -864,6 +1095,8 @@ public class PhysicsAnimationLayout extends FrameLayout {
mStiffness = -1;
mDampingRatio = -1;
mEndActionsForProperty.clear();
+ mPathAnimator = null;
+ mPositionEndActions = null;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index ab8752e4195f..2ec09a9dbea2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -23,6 +23,7 @@ import android.util.Log;
import android.view.View;
import android.view.WindowInsets;
+import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FlingAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
@@ -33,6 +34,8 @@ import com.android.systemui.R;
import com.google.android.collect.Sets;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Set;
@@ -53,6 +56,10 @@ public class StackAnimationController extends
/** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */
private static final int ANIMATE_TRANSLATION_FACTOR = 4;
+ /** Values to use for animating bubbles in. */
+ private static final float ANIMATE_IN_STIFFNESS = 1000f;
+ private static final int ANIMATE_IN_START_DELAY = 25;
+
/**
* Values to use for the default {@link SpringForce} provided to the physics animation layout.
*/
@@ -92,7 +99,7 @@ public class StackAnimationController extends
private boolean mStackMovedToStartPosition = false;
/** The most recent position in which the stack was resting on the edge of the screen. */
- private PointF mRestingStackPosition;
+ @Nullable private PointF mRestingStackPosition;
/** The height of the most recently visible IME. */
private float mImeHeight = 0f;
@@ -139,14 +146,16 @@ public class StackAnimationController extends
/** Horizontal offset of bubbles in the stack. */
private float mStackOffset;
- /** Diameter of the bubbles themselves. */
- private int mIndividualBubbleSize;
+ /** Diameter of the bubble icon. */
+ private int mBubbleIconBitmapSize;
+ /** Width of the bubble (icon and padding). */
+ private int mBubbleSize;
/**
* The amount of space to add between the bubbles and certain UI elements, such as the top of
* the screen or the IME. This does not apply to the left/right sides of the screen since the
* stack goes offscreen intentionally.
*/
- private int mBubblePadding;
+ private int mBubblePaddingTop;
/** How far offscreen the stack rests. */
private int mBubbleOffscreen;
/** How far down the screen the stack starts, when there is no pre-existing location. */
@@ -185,7 +194,7 @@ public class StackAnimationController extends
return false;
}
- float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2;
+ float stackCenter = mStackPosition.x + mBubbleIconBitmapSize / 2;
float screenCenter = mLayout.getWidth() / 2;
return stackCenter < screenCenter;
}
@@ -197,18 +206,18 @@ public class StackAnimationController extends
*/
public void springStack(float destinationX, float destinationY) {
springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
- new SpringForce()
+ new SpringForce()
.setStiffness(SPRING_AFTER_FLING_STIFFNESS)
.setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
- 0 /* startXVelocity */,
- destinationX);
+ 0 /* startXVelocity */,
+ destinationX);
springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y,
- new SpringForce()
+ new SpringForce()
.setStiffness(SPRING_AFTER_FLING_STIFFNESS)
.setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
- 0 /* startYVelocity */,
- destinationY);
+ 0 /* startYVelocity */,
+ destinationY);
}
/**
@@ -218,7 +227,7 @@ public class StackAnimationController extends
* @return The X value that the stack will end up at after the fling/spring.
*/
public float flingStackThenSpringToEdge(float x, float velX, float velY) {
- final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
+ final boolean stackOnLeftSide = x - mBubbleIconBitmapSize / 2 < mLayout.getWidth() / 2;
final boolean stackShouldFlingLeft = stackOnLeftSide
? velX < ESCAPE_VELOCITY
@@ -230,6 +239,12 @@ public class StackAnimationController extends
final float destinationRelativeX = stackShouldFlingLeft
? stackBounds.left : stackBounds.right;
+ // If all bubbles were removed during a drag event, just return the X we would have animated
+ // to if there were still bubbles.
+ if (mLayout == null || mLayout.getChildCount() == 0) {
+ return destinationRelativeX;
+ }
+
// Minimum velocity required for the stack to make it to the targeted side of the screen,
// taking friction into account (4.2f is the number that friction scalars are multiplied by
// in DynamicAnimation.DragForce). This is an estimate - it could possibly be slightly off,
@@ -262,15 +277,6 @@ public class StackAnimationController extends
.setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
/* destination */ null);
- mLayout.setEndActionForMultipleProperties(
- () -> {
- mRestingStackPosition = new PointF();
- mRestingStackPosition.set(mStackPosition);
- mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
- mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
- },
- DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
// If we're flinging now, there's no more touch event to catch up to.
mFirstBubbleSpringingToTouch = false;
mIsMovingFromFlinging = true;
@@ -304,6 +310,18 @@ public class StackAnimationController extends
setStackPosition(new PointF(x, y));
}
+ /** Description of current animation controller state. */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("StackAnimationController state:");
+ pw.print(" isActive: "); pw.println(isActiveController());
+ pw.print(" restingStackPos: ");
+ pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
+ pw.print(" currentStackPos: "); pw.println(mStackPosition.toString());
+ pw.print(" isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
+ pw.print(" withinDismiss: "); pw.println(mWithinDismissTarget);
+ pw.print(" firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
+ }
+
/**
* 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
@@ -317,7 +335,7 @@ public class StackAnimationController extends
SpringForce spring,
Float finalPosition) {
Log.d(TAG, String.format("Flinging %s.",
- PhysicsAnimationLayout.getReadablePropertyName(property)));
+ PhysicsAnimationLayout.getReadablePropertyName(property)));
StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
final float currentValue = firstBubbleProperty.getValue(this);
@@ -347,6 +365,9 @@ public class StackAnimationController extends
.addEndListener((animation, canceled, endValue, endVelocity) -> {
if (!canceled) {
+ mRestingStackPosition = new PointF();
+ mRestingStackPosition.set(mStackPosition);
+
springFirstBubbleWithStackFollowing(property, spring, endVelocity,
finalPosition != null
? finalPosition
@@ -368,8 +389,8 @@ public class StackAnimationController extends
cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
- mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
- mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
+ removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+ removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
}
/** Save the current IME height so that we know where the stack bounds should be. */
@@ -427,7 +448,7 @@ public class StackAnimationController extends
: 0);
allowableRegion.right =
mLayout.getWidth()
- - mIndividualBubbleSize
+ - mBubbleSize
+ mBubbleOffscreen
- Math.max(
insets.getSystemWindowInsetRight(),
@@ -436,7 +457,7 @@ public class StackAnimationController extends
: 0);
allowableRegion.top =
- mBubblePadding
+ mBubblePaddingTop
+ Math.max(
mStatusBarHeight,
insets.getDisplayCutout() != null
@@ -444,9 +465,9 @@ public class StackAnimationController extends
: 0);
allowableRegion.bottom =
mLayout.getHeight()
- - mIndividualBubbleSize
- - mBubblePadding
- - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePadding : 0f)
+ - mBubbleSize
+ - mBubblePaddingTop
+ - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
- Math.max(
insets.getSystemWindowInsetBottom(),
insets.getDisplayCutout() != null
@@ -516,13 +537,19 @@ public class StackAnimationController extends
mWithinDismissTarget = true;
mFirstBubbleSpringingToTouch = false;
- animationForChildAtIndex(0)
- .translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
- .translationY(destY, after)
- .withPositionStartVelocities(velX, velY)
- .withStiffness(SpringForce.STIFFNESS_MEDIUM)
- .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- .start();
+ springFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_X,
+ new SpringForce()
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM),
+ velX, mLayout.getWidth() / 2f - mBubbleIconBitmapSize / 2f);
+
+ springFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_Y,
+ new SpringForce()
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM),
+ velY, destY, after);
}
/**
@@ -550,7 +577,7 @@ public class StackAnimationController extends
*/
protected void springFirstBubbleWithStackFollowing(
DynamicAnimation.ViewProperty property, SpringForce spring,
- float vel, float finalPosition) {
+ float vel, float finalPosition, @Nullable Runnable... after) {
if (mLayout.getChildCount() == 0) {
return;
@@ -564,6 +591,13 @@ public class StackAnimationController extends
SpringAnimation springAnimation =
new SpringAnimation(this, firstBubbleProperty)
.setSpring(spring)
+ .addEndListener((dynamicAnimation, b, v, v1) -> {
+ if (after != null) {
+ for (Runnable callback : after) {
+ callback.run();
+ }
+ }
+ })
.setStartVelocity(vel);
cancelStackPositionAnimation(property);
@@ -620,13 +654,18 @@ public class StackAnimationController extends
@Override
void onChildAdded(View child, int index) {
+ // Don't animate additions within the dismiss target.
+ if (mWithinDismissTarget) {
+ return;
+ }
+
if (mLayout.getChildCount() == 1) {
// If this is the first child added, position the stack in its starting position.
moveStackToStartPosition();
} else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
// Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
// to the back of the stack, it'll be largely invisible so don't bother animating it in.
- animateInBubble(child);
+ animateInBubble(child, index);
}
}
@@ -641,24 +680,29 @@ public class StackAnimationController extends
.translationX(mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR))
.start();
+ // If there are other bubbles, pull them into the correct position.
if (mLayout.getChildCount() > 0) {
animationForChildAtIndex(0).translationX(mStackPosition.x).start();
} else {
- // Set the start position back to the default since we're out of bubbles. New bubbles
- // will then animate in from the start position.
- mStackPosition = getDefaultStartPosition();
+ // If there's no other bubbles, and we were in the dismiss target, reset the flag.
+ mWithinDismissTarget = false;
}
}
@Override
- void onChildReordered(View child, int oldIndex, int newIndex) {}
+ void onChildReordered(View child, int oldIndex, int newIndex) {
+ if (isStackPositionSet()) {
+ setStackPosition(mStackPosition);
+ }
+ }
@Override
void onActiveControllerForLayout(PhysicsAnimationLayout 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);
+ mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackStartingVerticalOffset =
res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
@@ -666,6 +710,20 @@ public class StackAnimationController extends
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
}
+ /**
+ * Update effective screen width based on current orientation.
+ * @param orientation Landscape or portrait.
+ */
+ public void updateOrientation(int orientation) {
+ if (mLayout != null) {
+ Resources res = mLayout.getContext().getResources();
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ mStatusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ }
+ }
+
+
/** Moves the stack, without any animation, to the starting position. */
private void moveStackToStartPosition() {
// Post to ensure that the layout's width and height have been calculated.
@@ -679,7 +737,7 @@ public class StackAnimationController extends
// Animate in the top bubble now that we're visible.
if (mLayout.getChildCount() > 0) {
- animateInBubble(mLayout.getChildAt(0));
+ animateInBubble(mLayout.getChildAt(0), 0 /* index */);
}
});
}
@@ -715,7 +773,9 @@ public class StackAnimationController extends
// If we're not the active controller, we don't want to physically move the bubble views.
if (isActiveController()) {
- mLayout.cancelAllAnimations();
+ // Cancel animations that could be moving the views.
+ mLayout.cancelAllAnimationsOfProperties(
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
cancelStackPositionAnimations();
// Since we're not using the chained animations, apply the offsets manually.
@@ -742,21 +802,34 @@ public class StackAnimationController extends
}
/** Animates in the given bubble. */
- private void animateInBubble(View child) {
+ private void animateInBubble(View child, int index) {
if (!isActiveController()) {
return;
}
+ final float xOffset =
+ getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+
+ // Position the new bubble in the correct position, scaled down completely.
+ child.setTranslationX(mStackPosition.x + xOffset * index);
child.setTranslationY(mStackPosition.y);
+ child.setScaleX(0f);
+ child.setScaleY(0f);
+
+ // Push the subsequent views out of the way, if there are subsequent views.
+ if (index + 1 < mLayout.getChildCount()) {
+ animationForChildAtIndex(index + 1)
+ .translationX(mStackPosition.x + xOffset * (index + 1))
+ .withStiffness(SpringForce.STIFFNESS_LOW)
+ .start();
+ }
- float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+ // Scale in the new bubble, slightly delayed.
animationForChild(child)
- .scaleX(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
- .scaleY(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
- .alpha(0f /* from */, 1f /* to */)
- .translationX(
- mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset /* from */,
- mStackPosition.x /* to */)
+ .scaleX(1f)
+ .scaleY(1f)
+ .withStiffness(ANIMATE_IN_STIFFNESS)
+ .withStartDelay(mLayout.getChildCount() > 1 ? ANIMATE_IN_START_DELAY : 0)
.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 6e75c0375afc..66a06193d0bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -28,7 +28,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -85,7 +85,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
* possible.
*/
private final boolean mAlwaysExpandNonGroupedNotification;
- private final BubbleData mBubbleData;
+ private final BubbleController mBubbleController;
private final DynamicPrivacyController mDynamicPrivacyController;
private final KeyguardBypassController mBypassController;
@@ -107,8 +107,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
Lazy<ShadeController> shadeController,
- BubbleData bubbleData,
KeyguardBypassController bypassController,
+ BubbleController bubbleController,
DynamicPrivacyController privacyController) {
mHandler = mainHandler;
mLockscreenUserManager = notificationLockscreenUserManager;
@@ -121,7 +121,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mBubbleData = bubbleData;
+ mBubbleController = bubbleController;
mDynamicPrivacyController = privacyController;
privacyController.addListener(this);
}
@@ -147,7 +147,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
if (ent.isRowDismissed() || ent.isRowRemoved()
- || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) {
+ || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.key)) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
continue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 154d7b356cd1..f99662e69b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -133,11 +133,6 @@ public class NotificationFilter {
}
}
}
-
- if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
- return true;
- }
-
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 68d95463bd3a..150667b86828 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -39,7 +39,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
@@ -57,9 +56,8 @@ public class NotificationInterruptionStateProvider {
private static final boolean ENABLE_HEADS_UP = true;
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
- private final StatusBarStateController mStatusBarStateController =
- Dependency.get(StatusBarStateController.class);
- private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+ private final StatusBarStateController mStatusBarStateController;
+ private final NotificationFilter mNotificationFilter;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final Context mContext;
@@ -67,7 +65,6 @@ public class NotificationInterruptionStateProvider {
private final IDreamManager mDreamManager;
private NotificationPresenter mPresenter;
- private ShadeController mShadeController;
private HeadsUpManager mHeadsUpManager;
private HeadsUpSuppressor mHeadsUpSuppressor;
@@ -77,12 +74,15 @@ public class NotificationInterruptionStateProvider {
private boolean mDisableNotificationAlerts;
@Inject
- public NotificationInterruptionStateProvider(Context context) {
+ public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
+ StatusBarStateController stateController) {
this(context,
(PowerManager) context.getSystemService(Context.POWER_SERVICE),
IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE)),
- new AmbientDisplayConfiguration(context));
+ new AmbientDisplayConfiguration(context),
+ filter,
+ stateController);
}
@VisibleForTesting
@@ -90,11 +90,15 @@ public class NotificationInterruptionStateProvider {
Context context,
PowerManager powerManager,
IDreamManager dreamManager,
- AmbientDisplayConfiguration ambientDisplayConfiguration) {
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ NotificationFilter notificationFilter,
+ StatusBarStateController statusBarStateController) {
mContext = context;
mPowerManager = powerManager;
mDreamManager = dreamManager;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+ mNotificationFilter = notificationFilter;
+ mStatusBarStateController = statusBarStateController;
}
/** Sets up late-binding dependencies for this component. */
@@ -102,29 +106,39 @@ public class NotificationInterruptionStateProvider {
NotificationPresenter notificationPresenter,
HeadsUpManager headsUpManager,
HeadsUpSuppressor headsUpSuppressor) {
+ setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor,
+ new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean wasUsing = mUseHeadsUp;
+ mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+ && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ Settings.Global.HEADS_UP_OFF);
+ Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+ if (wasUsing != mUseHeadsUp) {
+ if (!mUseHeadsUp) {
+ Log.d(TAG,
+ "dismissing any existing heads up notification on disable"
+ + " event");
+ mHeadsUpManager.releaseAllImmediately();
+ }
+ }
+ }
+ });
+ }
+
+ /** Sets up late-binding dependencies for this component. */
+ public void setUpWithPresenter(
+ NotificationPresenter notificationPresenter,
+ HeadsUpManager headsUpManager,
+ HeadsUpSuppressor headsUpSuppressor,
+ ContentObserver observer) {
mPresenter = notificationPresenter;
mHeadsUpManager = headsUpManager;
mHeadsUpSuppressor = headsUpSuppressor;
-
- mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
- @Override
- public void onChange(boolean selfChange) {
- boolean wasUsing = mUseHeadsUp;
- mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
- && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
- Settings.Global.HEADS_UP_OFF);
- Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
- if (wasUsing != mUseHeadsUp) {
- if (!mUseHeadsUp) {
- Log.d(TAG,
- "dismissing any existing heads up notification on disable event");
- mHeadsUpManager.releaseAllImmediately();
- }
- }
- }
- };
+ mHeadsUpObserver = observer;
if (ENABLE_HEADS_UP) {
mContext.getContentResolver().registerContentObserver(
@@ -138,13 +152,6 @@ public class NotificationInterruptionStateProvider {
mHeadsUpObserver.onChange(true); // set up
}
- private ShadeController getShadeController() {
- if (mShadeController == null) {
- mShadeController = Dependency.get(ShadeController.class);
- }
- return mShadeController;
- }
-
/**
* Whether the notification should appear as a bubble with a fly-out on top of the screen.
*
@@ -153,6 +160,15 @@ public class NotificationInterruptionStateProvider {
*/
public boolean shouldBubbleUp(NotificationEntry entry) {
final StatusBarNotification sbn = entry.notification;
+
+ if (!canAlertCommon(entry)) {
+ return false;
+ }
+
+ if (!canAlertAwakeCommon(entry)) {
+ return false;
+ }
+
if (!entry.canBubble) {
if (DEBUG) {
Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
@@ -177,10 +193,6 @@ public class NotificationInterruptionStateProvider {
return false;
}
- if (!canHeadsUpCommon(entry)) {
- return false;
- }
-
return true;
}
@@ -201,23 +213,34 @@ public class NotificationInterruptionStateProvider {
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
- boolean inShade = mStatusBarStateController.getState() == SHADE;
- if (entry.isBubble() && inShade) {
+ if (!mUseHeadsUp) {
if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
- + "bubble: " + sbn.getKey());
+ Log.d(TAG, "No heads up: no huns");
}
return false;
}
if (!canAlertCommon(entry)) {
+ return false;
+ }
+
+ if (!canAlertAwakeCommon(entry)) {
+ return false;
+ }
+
+ boolean inShade = mStatusBarStateController.getState() == SHADE;
+ if (entry.isBubble() && inShade) {
if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+ Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
+ + "bubble: " + sbn.getKey());
}
return false;
}
- if (!canHeadsUpCommon(entry)) {
+ if (entry.shouldSuppressPeek()) {
+ if (DEBUG_HEADS_UP) {
+ Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+ }
return false;
}
@@ -294,16 +317,13 @@ public class NotificationInterruptionStateProvider {
}
/**
- * Common checks between regular heads up and when pulsing. See
- * {@link #shouldHeadsUp(NotificationEntry)} and
- * {@link #shouldHeadsUpWhenDozing(NotificationEntry)}. Notifications that fail any of these
- * checks
- * should not alert at all.
+ * Common checks between regular & AOD heads up and bubbles.
*
* @param entry the entry to check
* @return true if these checks pass, false if the notification should not alert
*/
- protected boolean canAlertCommon(NotificationEntry entry) {
+ @VisibleForTesting
+ public boolean canAlertCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -320,46 +340,36 @@ public class NotificationInterruptionStateProvider {
}
return false;
}
-
return true;
}
/**
- * Common checks between heads up alerting and bubble fly out alerting. See
- * {@link #shouldHeadsUp(NotificationEntry)} and
- * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
- * checks should not interrupt the user on screen.
+ * Common checks between alerts that occur while the device is awake (heads up & bubbles).
*
* @param entry the entry to check
- * @return true if these checks pass, false if the notification should not interrupt on screen
+ * @return true if these checks pass, false if the notification should not alert
*/
- public boolean canHeadsUpCommon(NotificationEntry entry) {
+ @VisibleForTesting
+ public boolean canAlertAwakeCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
- if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
- if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No heads up: no huns or vr mode");
- }
- return false;
- }
-
- if (entry.shouldSuppressPeek()) {
+ if (mPresenter.isDeviceInVrMode()) {
if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+ Log.d(TAG, "No alerting: no huns or vr mode");
}
return false;
}
if (isSnoozedPackage(sbn)) {
if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+ Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
}
return false;
}
if (entry.hasJustLaunchedFullScreenIntent()) {
if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+ Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
}
return false;
}
@@ -377,6 +387,18 @@ public class NotificationInterruptionStateProvider {
mHeadsUpObserver.onChange(true);
}
+ /** Whether all alerts are disabled. */
+ @VisibleForTesting
+ public boolean areNotificationAlertsDisabled() {
+ return mDisableNotificationAlerts;
+ }
+
+ /** Whether HUNs should be used. */
+ @VisibleForTesting
+ public boolean getUseHeadsUp() {
+ return mUseHeadsUp;
+ }
+
protected NotificationPresenter getPresenter() {
return mPresenter;
}
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 6178488cf43a..2964889f1399 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
@@ -23,6 +23,7 @@ import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
@@ -41,7 +42,6 @@ import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
import android.widget.ImageView;
@@ -52,7 +52,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.InflationException;
@@ -158,19 +157,6 @@ public final class NotificationEntry {
public boolean canBubble;
/**
- * Whether this notification should be shown in the shade when it is also displayed as a bubble.
- *
- * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
- * expanded</p>
- */
- private boolean mShowInShadeWhenBubble;
-
- /**
- * Whether the user has dismissed this notification when it was in bubble form.
- */
- 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.
*/
@@ -305,31 +291,6 @@ public final class NotificationEntry {
return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
}
- public void setBubbleDismissed(boolean userDismissed) {
- mUserDismissedBubble = userDismissed;
- }
-
- public boolean isBubbleDismissed() {
- return mUserDismissedBubble;
- }
-
- /**
- * Sets whether this notification should be shown in the shade when it is also displayed as a
- * bubble.
- */
- public void setShowInShadeWhenBubble(boolean showInShade) {
- mShowInShadeWhenBubble = showInShade;
- }
-
- /**
- * Whether this notification should be shown in the shade when it is also displayed as a
- * bubble.
- */
- public boolean showInShadeWhenBubble() {
- // We always show it in the shade if non-clearable
- return !isRowDismissed() && (!isClearable() || mShowInShadeWhenBubble);
- }
-
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
@@ -524,72 +485,6 @@ public final class NotificationEntry {
}
/**
- * Returns our best guess for the most relevant text summary of the latest update to this
- * notification, based on its type. Returns null if there should not be an update message.
- */
- public CharSequence getUpdateMessage(Context context) {
- final Notification underlyingNotif = notification.getNotification();
- final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
- try {
- if (Notification.BigTextStyle.class.equals(style)) {
- // Return the big text, it is big so probably important. If it's not there use the
- // normal text.
- CharSequence bigText =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
- return !TextUtils.isEmpty(bigText)
- ? bigText
- : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- } else if (Notification.MessagingStyle.class.equals(style)) {
- final List<Notification.MessagingStyle.Message> messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- (Parcelable[]) underlyingNotif.extras.get(
- Notification.EXTRA_MESSAGES));
-
- final Notification.MessagingStyle.Message latestMessage =
- Notification.MessagingStyle.findLatestIncomingMessage(messages);
-
- if (latestMessage != null) {
- final CharSequence personName = latestMessage.getSenderPerson() != null
- ? latestMessage.getSenderPerson().getName()
- : null;
-
- // Prepend the sender name if available since group chats also use messaging
- // style.
- if (!TextUtils.isEmpty(personName)) {
- return context.getResources().getString(
- R.string.notification_summary_message_format,
- personName,
- latestMessage.getText());
- } else {
- return latestMessage.getText();
- }
- }
- } else if (Notification.InboxStyle.class.equals(style)) {
- CharSequence[] lines =
- underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
- // Return the last line since it should be the most recent.
- if (lines != null && lines.length > 0) {
- return lines[lines.length - 1];
- }
- } else if (Notification.MediaStyle.class.equals(style)) {
- // Return nothing, media updates aren't typically useful as a text update.
- return null;
- } else {
- // Default to text extra.
- return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- }
- } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
- // No use crashing, we'll just return null and the caller will assume there's no update
- // message.
- e.printStackTrace();
- }
-
- return null;
- }
-
- /**
* Abort all existing inflation tasks
*/
public void abortTask() {
@@ -936,6 +831,16 @@ public final class NotificationEntry {
return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
}
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_BADGE}
+ * is set for this entry. This badge is not an app badge, but rather an indicator of "unseen"
+ * content. Typically this is referred to as a "dot" internally in Launcher & SysUI code.
+ */
+ public boolean shouldSuppressNotificationDot() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_BADGE);
+ }
+
/**
* Categories that are explicitly called out on DND settings screens are always blocked, if
* DND has flagged them, even if they are foreground or system notifications that might
@@ -1003,12 +908,4 @@ public final class NotificationEntry {
this.index = index;
}
}
-
- /**
- * Returns whether the notification is a foreground service. It shows that this is an ongoing
- * bubble.
- */
- public boolean isForegroundService() {
- return (notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
- }
}
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 65e744b9e047..12d537d3c646 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
@@ -2311,9 +2311,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public int getIntrinsicHeight() {
- if (isShownAsBubble()) {
- return getMaxExpandHeight();
- }
if (isUserLocked()) {
return getActualHeight();
}
@@ -2359,10 +2356,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mStatusbarStateController != null && mStatusbarStateController.isDozing();
}
- private boolean isShownAsBubble() {
- return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
- }
-
@Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 06a2225ed0bf..ecfc45bb1182 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -604,7 +604,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange
*/
public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
if (mWakeAndUnlockRunning
- && scrimsVisible == ScrimController.VISIBILITY_FULLY_TRANSPARENT) {
+ && scrimsVisible == ScrimController.TRANSPARENT) {
mWakeAndUnlockRunning = false;
update();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 195870bde25a..adaea9379c71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -22,6 +22,7 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.systemui.Dependency;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
@@ -53,12 +54,20 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
private boolean mIsUpdatingUnchangedGroup;
+ @Nullable private BubbleController mBubbleController = null;
@Inject
public NotificationGroupManager(StatusBarStateController statusBarStateController) {
statusBarStateController.addCallback(this);
}
+ private BubbleController getBubbleController() {
+ if (mBubbleController == null) {
+ mBubbleController = Dependency.get(BubbleController.class);
+ }
+ return mBubbleController;
+ }
+
/**
* Add a listener for changes to groups.
*
@@ -187,12 +196,22 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
if (group == null) {
return;
}
+ int childCount = 0;
+ boolean hasBubbles = false;
+ for (String key : group.children.keySet()) {
+ if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) {
+ childCount++;
+ } else {
+ hasBubbles = true;
+ }
+ }
+
boolean prevSuppressed = group.suppressed;
group.suppressed = group.summary != null && !group.expanded
- && (group.children.size() == 1
- || (group.children.size() == 0
+ && (childCount == 1
+ || (childCount == 0
&& group.summary.notification.getNotification().isGroupSummary()
- && hasIsolatedChildren(group)));
+ && (hasIsolatedChildren(group) || hasBubbles)));
if (prevSuppressed != group.suppressed) {
for (OnGroupChangeListener listener : mListeners) {
if (!mIsUpdatingUnchangedGroup) {
@@ -381,6 +400,17 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
}
/**
+ * If there is a {@link NotificationGroup} associated with the provided entry, this method
+ * will update the suppression of that group.
+ */
+ public void updateSuppression(NotificationEntry entry) {
+ NotificationGroup group = mGroupMap.get(getGroupKey(entry.notification));
+ if (group != null) {
+ updateSuppression(group);
+ }
+ }
+
+ /**
* Get the group key. May differ from the one in the notification due to the notification
* being temporarily isolated.
*
@@ -565,6 +595,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
? Log.getStackTraceString(child.getDebugThrowable())
: "");
}
+ result += "\n summary suppressed: " + suppressed;
return result;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a7262cfcfefb..5733d4b0eb28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -79,23 +79,24 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
/**
* When both scrims have 0 alpha.
*/
- public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+ public static final int TRANSPARENT = 0;
/**
* When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
*/
- public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+ public static final int SEMI_TRANSPARENT = 1;
/**
* When at least 1 scrim is fully opaque (alpha set to 1.)
*/
- public static final int VISIBILITY_FULLY_OPAQUE = 2;
+ public static final int OPAQUE = 2;
- @IntDef(prefix = { "VISIBILITY_" }, value = {
- VISIBILITY_FULLY_TRANSPARENT,
- VISIBILITY_SEMI_TRANSPARENT,
- VISIBILITY_FULLY_OPAQUE
+ @IntDef(prefix = {"VISIBILITY_"}, value = {
+ TRANSPARENT,
+ SEMI_TRANSPARENT,
+ OPAQUE
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ScrimVisibility {}
+ public @interface ScrimVisibility {
+ }
/**
* Default alpha value for most scrims.
@@ -123,8 +124,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private ScrimState mState = ScrimState.UNINITIALIZED;
private final Context mContext;
- protected final ScrimView mScrimBehind;
+
protected final ScrimView mScrimInFront;
+ protected final ScrimView mScrimBehind;
+ protected final ScrimView mScrimForBubble;
+
private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
@@ -153,10 +157,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private float mCurrentInFrontAlpha = NOT_INITIALIZED;
- private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private int mCurrentInFrontTint;
- private int mCurrentBehindTint;
+
+ private float mInFrontAlpha = NOT_INITIALIZED;
+ private float mBehindAlpha = NOT_INITIALIZED;
+ private float mBubbleAlpha = NOT_INITIALIZED;
+
+ private int mInFrontTint;
+ private int mBehindTint;
+ private int mBubbleTint;
+
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
@@ -175,14 +184,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private boolean mWakeLockHeld;
private boolean mKeyguardOccluded;
- public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+ public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble,
TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
+ mScrimForBubble = scrimForBubble;
+
mScrimStateListener = scrimStateListener;
mScrimVisibleListener = scrimVisibleListener;
+
mContext = scrimBehind.getContext();
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
@@ -213,12 +225,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
- states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
}
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
+ mScrimForBubble.setDefaultFocusHighlightEnabled(false);
updateScrims();
}
@@ -257,10 +270,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mBlankScreen = state.getBlanksScreen();
mAnimateChange = state.getAnimateChange();
mAnimationDuration = state.getAnimationDuration();
- mCurrentInFrontTint = state.getFrontTint();
- mCurrentBehindTint = state.getBehindTint();
- mCurrentInFrontAlpha = state.getFrontAlpha();
- mCurrentBehindAlpha = state.getBehindAlpha();
+
+ mInFrontTint = state.getFrontTint();
+ mBehindTint = state.getBehindTint();
+ mBubbleTint = state.getBubbleTint();
+
+ mInFrontAlpha = state.getFrontAlpha();
+ mBehindAlpha = state.getBehindAlpha();
+ mBubbleAlpha = state.getBubbleAlpha();
applyExpansionToAlpha();
// Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -393,21 +410,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
if (mExpansionFraction != fraction) {
mExpansionFraction = fraction;
- final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
- || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
- if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
+ boolean relevantState = (mState == ScrimState.UNLOCKED
+ || mState == ScrimState.KEYGUARD
+ || mState == ScrimState.PULSING
+ || mState == ScrimState.BUBBLE_EXPANDED);
+ if (!(relevantState && mExpansionAffectsAlpha)) {
return;
}
-
applyExpansionToAlpha();
-
if (mUpdatePending) {
return;
}
-
setOrAdaptCurrentAnimation(mScrimBehind);
setOrAdaptCurrentAnimation(mScrimInFront);
-
+ setOrAdaptCurrentAnimation(mScrimForBubble);
dispatchScrimState(mScrimBehind.getViewAlpha());
// Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
@@ -421,11 +437,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
private void setOrAdaptCurrentAnimation(View scrim) {
- if (!isAnimating(scrim)) {
- updateScrimColor(scrim, getCurrentScrimAlpha(scrim), getCurrentScrimTint(scrim));
- } else {
+ float alpha = getCurrentScrimAlpha(scrim);
+ if (isAnimating(scrim)) {
+ // Adapt current animation.
ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
- float alpha = getCurrentScrimAlpha(scrim);
float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
float relativeDiff = alpha - previousEndValue;
@@ -433,6 +448,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
scrim.setTag(TAG_START_ALPHA, newStartValue);
scrim.setTag(TAG_END_ALPHA, alpha);
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+ } else {
+ // Set animation.
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
}
}
@@ -441,27 +459,27 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
return;
}
- if (mState == ScrimState.UNLOCKED) {
+ if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
// Darken scrim as you pull down the shade when unlocked
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
- mCurrentInFrontAlpha = 0;
+ mBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
+ mInFrontAlpha = 0;
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
// Either darken of make the scrim transparent when you
// pull down the shade
float interpolatedFract = getInterpolatedFraction();
float alphaBehind = mState.getBehindAlpha();
if (mDarkenWhileDragging) {
- mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
+ mBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
interpolatedFract);
- mCurrentInFrontAlpha = 0;
+ mInFrontAlpha = 0;
} else {
- mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
+ mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
interpolatedFract);
- mCurrentInFrontAlpha = 0;
+ mInFrontAlpha = 0;
}
- mCurrentBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+ mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
mState.getBehindTint(), interpolatedFract);
}
}
@@ -486,8 +504,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
*/
public void setAodFrontScrimAlpha(float alpha) {
if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
- && mCurrentInFrontAlpha != alpha) {
- mCurrentInFrontAlpha = alpha;
+ && mInFrontAlpha != alpha) {
+ mInFrontAlpha = alpha;
updateScrims();
}
@@ -499,10 +517,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
* away once the display turns on.
*/
public void prepareForGentleWakeUp() {
- if (mState == ScrimState.AOD) {
- mCurrentInFrontAlpha = 1f;
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
+ if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+ mInFrontAlpha = 1f;
+ mInFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
mAnimateChange = false;
updateScrims();
mAnimateChange = true;
@@ -520,8 +538,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
if (mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
- if (mCurrentBehindAlpha != newBehindAlpha) {
- mCurrentBehindAlpha = newBehindAlpha;
+ if (mBehindAlpha != newBehindAlpha) {
+ mBehindAlpha = newBehindAlpha;
updateScrims();
}
}
@@ -543,8 +561,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// Only animate scrim color if the scrim view is actually visible
boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
+ boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+
mScrimInFront.setColors(mColors, animateScrimInFront);
mScrimBehind.setColors(mColors, animateScrimBehind);
+ mScrimForBubble.setColors(mColors, animateScrimForBubble);
// Calculate minimum scrim opacity for white or black text.
int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -563,12 +584,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
&& mKeyguardOccluded;
if (aodWallpaperTimeout || occludedKeyguard) {
- mCurrentBehindAlpha = 1;
+ mBehindAlpha = 1;
}
-
- setScrimInFrontAlpha(mCurrentInFrontAlpha);
- setScrimBehindAlpha(mCurrentBehindAlpha);
-
+ setScrimAlpha(mScrimInFront, mInFrontAlpha);
+ setScrimAlpha(mScrimBehind, mBehindAlpha);
+ setScrimAlpha(mScrimForBubble, mBubbleAlpha);
dispatchScrimsVisible();
}
@@ -579,11 +599,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private void dispatchScrimsVisible() {
final int currentScrimVisibility;
if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
- currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+ currentScrimVisibility = OPAQUE;
} else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
- currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+ currentScrimVisibility = TRANSPARENT;
} else {
- currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+ currentScrimVisibility = SEMI_TRANSPARENT;
}
if (mScrimsVisibility != currentScrimVisibility) {
@@ -600,18 +620,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
return 0;
} else {
// woo, special effects
- return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
+ return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
}
}
- private void setScrimBehindAlpha(float alpha) {
- setScrimAlpha(mScrimBehind, alpha);
- }
-
- private void setScrimInFrontAlpha(float alpha) {
- setScrimAlpha(mScrimInFront, alpha);
- }
-
private void setScrimAlpha(ScrimView scrim, float alpha) {
if (alpha == 0f) {
scrim.setClickable(false);
@@ -622,17 +634,26 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
updateScrim(scrim, alpha);
}
+ private String getScrimName(ScrimView scrim) {
+ if (scrim == mScrimInFront) {
+ return "front_scrim";
+ } else if (scrim == mScrimBehind) {
+ return "back_scrim";
+ } else if (scrim == mScrimForBubble) {
+ return "bubble_scrim";
+ }
+ return "unknown_scrim";
+ }
+
private void updateScrimColor(View scrim, float alpha, int tint) {
alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- Trace.traceCounter(Trace.TRACE_TAG_APP,
- scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
(int) (alpha * 255));
- Trace.traceCounter(Trace.TRACE_TAG_APP,
- scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
Color.alpha(tint));
scrimView.setTint(tint);
@@ -689,9 +710,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private float getCurrentScrimAlpha(View scrim) {
if (scrim == mScrimInFront) {
- return mCurrentInFrontAlpha;
+ return mInFrontAlpha;
} else if (scrim == mScrimBehind) {
- return mCurrentBehindAlpha;
+ return mBehindAlpha;
+ } else if (scrim == mScrimForBubble) {
+ return mBubbleAlpha;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
@@ -699,9 +722,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private int getCurrentScrimTint(View scrim) {
if (scrim == mScrimInFront) {
- return mCurrentInFrontTint;
+ return mInFrontTint;
} else if (scrim == mScrimBehind) {
- return mCurrentBehindTint;
+ return mBehindTint;
+ } else if (scrim == mScrimForBubble) {
+ return mBubbleTint;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
@@ -744,8 +769,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// When unlocking with fingerprint, we'll fade the scrims from black to transparent.
// At the end of the animation we need to remove the tint.
if (mState == ScrimState.UNLOCKED) {
- mCurrentInFrontTint = Color.TRANSPARENT;
- mCurrentBehindTint = Color.TRANSPARENT;
+ mInFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.TRANSPARENT;
+ mBubbleTint = Color.TRANSPARENT;
}
}
@@ -850,6 +876,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
/**
* Executes a callback after the frame has hit the display.
+ *
* @param callback What to run.
*/
@VisibleForTesting
@@ -893,16 +920,35 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" ScrimController: ");
- pw.print(" state: "); pw.println(mState);
- pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
- pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
-
- pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
- pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
-
- pw.print(" mTracking="); pw.println(mTracking);
+ pw.print(" state: ");
+ pw.println(mState);
+
+ pw.print(" frontScrim:");
+ pw.print(" viewAlpha=");
+ pw.print(mScrimInFront.getViewAlpha());
+ pw.print(" alpha=");
+ pw.print(mInFrontAlpha);
+ pw.print(" tint=0x");
+ pw.println(Integer.toHexString(mScrimInFront.getTint()));
+
+ pw.print(" backScrim:");
+ pw.print(" viewAlpha=");
+ pw.print(mScrimBehind.getViewAlpha());
+ pw.print(" alpha=");
+ pw.print(mBehindAlpha);
+ pw.print(" tint=0x");
+ pw.println(Integer.toHexString(mScrimBehind.getTint()));
+
+ pw.print(" bubbleScrim:");
+ pw.print(" viewAlpha=");
+ pw.print(mScrimForBubble.getViewAlpha());
+ pw.print(" alpha=");
+ pw.print(mBubbleAlpha);
+ pw.print(" tint=0x");
+ pw.println(Integer.toHexString(mScrimForBubble.getTint()));
+
+ pw.print(" mTracking=");
+ pw.println(mTracking);
}
public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
@@ -949,8 +995,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// in this case, back-scrim needs to be re-evaluated
if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
- if (mCurrentBehindAlpha != newBehindAlpha) {
- mCurrentBehindAlpha = newBehindAlpha;
+ if (mBehindAlpha != newBehindAlpha) {
+ mBehindAlpha = newBehindAlpha;
updateScrims();
}
}
@@ -971,10 +1017,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
public interface Callback {
default void onStart() {
}
+
default void onDisplayBlanked() {
}
+
default void onFinished() {
}
+
default void onCancelled() {
}
/** Returns whether to timeout wallpaper or not. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 9fdd3b88e9d0..c9acbad1e8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -36,7 +36,6 @@ public enum ScrimState {
* On the lock screen.
*/
KEYGUARD(0) {
-
@Override
public void prepare(ScrimState previousState) {
mBlankScreen = false;
@@ -53,10 +52,13 @@ public enum ScrimState {
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
}
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
- mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
- mCurrentInFrontAlpha = 0;
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.TRANSPARENT;
+
+ mFrontAlpha = 0;
+ mBehindAlpha = mScrimBehindAlphaKeyguard;
+ mBubbleAlpha = 0;
}
},
@@ -66,8 +68,9 @@ public enum ScrimState {
BOUNCER(1) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
- mCurrentInFrontAlpha = 0f;
+ mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mFrontAlpha = 0f;
+ mBubbleAlpha = 0f;
}
},
@@ -77,8 +80,9 @@ public enum ScrimState {
BOUNCER_SCRIMMED(2) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 0;
- mCurrentInFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mBehindAlpha = 0;
+ mBubbleAlpha = 0f;
+ mFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
}
},
@@ -88,8 +92,9 @@ public enum ScrimState {
BRIGHTNESS_MIRROR(3) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 0;
- mCurrentInFrontAlpha = 0;
+ mBehindAlpha = 0;
+ mFrontAlpha = 0;
+ mBubbleAlpha = 0;
}
},
@@ -101,9 +106,16 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
mBlankScreen = mDisplayRequiresBlanking;
- mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
+
+ mFrontTint = Color.BLACK;
+ mFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+
+ mBehindTint = Color.BLACK;
+ mBehindAlpha = ScrimController.TRANSPARENT;
+
+ mBubbleTint = Color.TRANSPARENT;
+ mBubbleAlpha = ScrimController.TRANSPARENT;
+
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
// DisplayPowerManager may blank the screen for us,
// in this case we just need to set our state.
@@ -127,9 +139,10 @@ public enum ScrimState {
PULSING(5) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentInFrontAlpha = 0f;
- mCurrentBehindTint = Color.BLACK;
- mCurrentInFrontTint = Color.BLACK;
+ mFrontAlpha = 0f;
+ mBubbleAlpha = 0f;
+ mBehindTint = Color.BLACK;
+ mFrontTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
mAnimationDuration = mWakeLockScreenSensorActive
? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -154,25 +167,33 @@ public enum ScrimState {
UNLOCKED(6) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 0;
- mCurrentInFrontAlpha = 0;
+ // State that UI will sync to.
+ mBehindAlpha = 0;
+ mFrontAlpha = 0;
+ mBubbleAlpha = 0;
+
mAnimationDuration = mKeyguardFadingAway
? mKeyguardFadingAwayDuration
: StatusBar.FADE_KEYGUARD_DURATION;
+
mAnimateChange = !mLaunchingAffordanceWithPreview;
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.TRANSPARENT;
+ mBubbleTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+
if (previousState == ScrimState.AOD) {
- // Fade from black to transparent when coming directly from AOD
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
- updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Set all scrims black, before they fade transparent.
+ updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+
// Scrims should still be black at the end of the transition.
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
mBlankScreen = true;
- } else {
- mCurrentInFrontTint = Color.TRANSPARENT;
- mCurrentBehindTint = Color.TRANSPARENT;
- mBlankScreen = false;
}
}
},
@@ -183,25 +204,36 @@ public enum ScrimState {
BUBBLE_EXPANDED(7) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentInFrontTint = Color.TRANSPARENT;
- mCurrentBehindTint = Color.TRANSPARENT;
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.TRANSPARENT;
+ mBubbleTint = Color.TRANSPARENT;
+
+ mFrontAlpha = ScrimController.TRANSPARENT;
+ mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mBubbleAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+
mAnimationDuration = ScrimController.ANIMATION_DURATION;
- mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
mBlankScreen = false;
}
};
boolean mBlankScreen = false;
long mAnimationDuration = ScrimController.ANIMATION_DURATION;
- int mCurrentInFrontTint = Color.TRANSPARENT;
- int mCurrentBehindTint = Color.TRANSPARENT;
+ int mFrontTint = Color.TRANSPARENT;
+ int mBehindTint = Color.TRANSPARENT;
+ int mBubbleTint = Color.TRANSPARENT;
+
boolean mAnimateChange = true;
- float mCurrentInFrontAlpha;
- float mCurrentBehindAlpha;
float mAodFrontScrimAlpha;
+ float mFrontAlpha;
+ float mBehindAlpha;
+ float mBubbleAlpha;
+
float mScrimBehindAlphaKeyguard;
ScrimView mScrimInFront;
ScrimView mScrimBehind;
+ ScrimView mScrimForBubble;
+
DozeParameters mDozeParameters;
boolean mDisplayRequiresBlanking;
boolean mWallpaperSupportsAmbientMode;
@@ -216,13 +248,17 @@ public enum ScrimState {
mIndex = index;
}
- public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
+ DozeParameters dozeParameters) {
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
+ mScrimForBubble = scrimForBubble;
+
mDozeParameters = dozeParameters;
mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
}
+ /** Prepare state for transition. */
public void prepare(ScrimState previousState) {
}
@@ -231,19 +267,27 @@ public enum ScrimState {
}
public float getFrontAlpha() {
- return mCurrentInFrontAlpha;
+ return mFrontAlpha;
}
public float getBehindAlpha() {
- return mCurrentBehindAlpha;
+ return mBehindAlpha;
+ }
+
+ public float getBubbleAlpha() {
+ return mBubbleAlpha;
}
public int getFrontTint() {
- return mCurrentInFrontTint;
+ return mFrontTint;
}
public int getBehindTint() {
- return mCurrentBehindTint;
+ return mBehindTint;
+ }
+
+ public int getBubbleTint() {
+ return mBubbleTint;
}
public long getAnimationDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 9fc3e476579a..1c857240114c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -824,6 +824,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
mZenController.addCallback(this);
NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
@@ -944,8 +945,10 @@ public class StatusBar extends SystemUI implements DemoMode,
ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
+ ScrimView scrimForBubble = mStatusBarWindow.findViewById(R.id.scrim_for_bubble);
+
mScrimController = SystemUIFactory.getInstance().createScrimController(
- scrimBehind, scrimInFront, mLockscreenWallpaper,
+ scrimBehind, scrimInFront, scrimForBubble, mLockscreenWallpaper,
(state, alpha, color) -> mLightBarController.setScrimState(state, alpha, color),
scrimsVisible -> {
if (mStatusBarWindowController != null) {
@@ -2417,6 +2420,10 @@ public class StatusBar extends SystemUI implements DemoMode,
pw.println(" mGroupManager: null");
}
+ if (mBubbleController != null) {
+ mBubbleController.dump(fd, pw, args);
+ }
+
if (mLightBarController != null) {
mLightBarController.dump(fd, pw, args);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index e85b147f7a34..58519b8e8a58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -223,7 +223,7 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat
}
final boolean scrimsOccludingWallpaper =
- state.scrimsVisibility == ScrimController.VISIBILITY_FULLY_OPAQUE;
+ state.scrimsVisibility == ScrimController.OPAQUE;
final boolean keyguardOrAod = state.keyguardShowing
|| (state.dozing && mDozeParameters.getAlwaysOn());
if (keyguardOrAod && !state.backdropShowing && !scrimsOccludingWallpaper) {
@@ -309,7 +309,7 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat
return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
|| state.headsUpShowing || state.bubblesShowing
- || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
+ || state.scrimsVisibility != ScrimController.TRANSPARENT);
}
private void applyFitsSystemWindows(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index abe3f2c18891..fc6e5e22c2e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -18,12 +18,12 @@ package com.android.systemui.statusbar.policy;
import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
import android.content.Context;
-import android.text.format.DateFormat;
import android.util.Log;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.BitSet;
@@ -254,6 +254,8 @@ public abstract class SignalController<T extends SignalController.State,
}
static class State {
+ // No locale as it's only used for logging purposes
+ private static SimpleDateFormat sSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
boolean connected;
boolean enabled;
boolean activityIn;
@@ -301,7 +303,7 @@ public abstract class SignalController<T extends SignalController.State,
.append("activityOut=").append(activityOut).append(',')
.append("activityDormant=").append(activityDormant).append(',')
.append("rssi=").append(rssi).append(',')
- .append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time));
+ .append("lastModified=").append(sSDF.format(time));
}
@Override
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 2221915a627a..ba434d4fd0bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.bubbles;
import static android.app.Notification.FLAG_BUBBLE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
@@ -28,6 +27,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,11 +43,10 @@ import static org.mockito.Mockito.when;
import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.graphics.drawable.Icon;
+import android.hardware.face.FaceManager;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -57,17 +57,21 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -82,20 +86,16 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubbleControllerTest extends SysuiTestCase {
- // Some APIs rely on the app being foreground, check is via pkg name
- private static final String FOREGROUND_TEST_PKG_NAME = "com.android.systemui.tests";
-
@Mock
private NotificationEntryManager mNotificationEntryManager;
@Mock
+ private NotificationGroupManager mNotificationGroupManager;
+ @Mock
private WindowManager mWindowManager;
@Mock
private IActivityManager mActivityManager;
@@ -108,6 +108,10 @@ public class BubbleControllerTest extends SysuiTestCase {
@Mock
private ZenModeConfig mZenModeConfig;
@Mock
+ private FaceManager mFaceManager;
+ @Mock
+ private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock
private SysuiStatusBarStateController mStatusBarStateController;
@Mock
private KeyguardBypassController mKeyguardBypassController;
@@ -126,8 +130,6 @@ public class BubbleControllerTest extends SysuiTestCase {
private NotificationTestHelper mNotificationTestHelper;
private ExpandableNotificationRow mRow;
private ExpandableNotificationRow mRow2;
- private ExpandableNotificationRow mAutoExpandRow;
- private ExpandableNotificationRow mSuppressNotifRow;
private ExpandableNotificationRow mNonBubbleNotifRow;
@Mock
@@ -146,6 +148,7 @@ public class BubbleControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mStatusBarView = new FrameLayout(mContext);
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
+ mContext.addMockSystemService(FaceManager.class, mFaceManager);
// Bubbles get added to status bar window view
mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
@@ -159,35 +162,31 @@ public class BubbleControllerTest extends SysuiTestCase {
mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
mNonBubbleNotifRow = mNotificationTestHelper.createRow();
- // Some bubbles want to auto expand
- Notification.BubbleMetadata autoExpandMetadata =
- getBuilder().setAutoExpandBubble(true).build();
- mAutoExpandRow = mNotificationTestHelper.createBubble(autoExpandMetadata,
- FOREGROUND_TEST_PKG_NAME);
-
- // Some bubbles want to suppress notifs
- Notification.BubbleMetadata suppressNotifMetadata =
- getBuilder().setSuppressNotification(true).build();
- mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata,
- FOREGROUND_TEST_PKG_NAME);
-
// Return non-null notification data from the NEM
when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
+ when(mNotificationData.get(mRow.getEntry().key)).thenReturn(mRow.getEntry());
when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
TestableNotificationInterruptionStateProvider interruptionStateProvider =
- new TestableNotificationInterruptionStateProvider(mContext);
+ new TestableNotificationInterruptionStateProvider(mContext,
+ mock(NotificationFilter.class),
+ mock(StatusBarStateController.class));
interruptionStateProvider.setUpWithPresenter(
mock(NotificationPresenter.class),
mock(HeadsUpManager.class),
mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
mBubbleData = new BubbleData(mContext);
- mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
- mBubbleData, mConfigurationController, interruptionStateProvider,
- mZenModeController);
+ mBubbleController = new TestableBubbleController(mContext,
+ mStatusBarWindowController,
+ mBubbleData,
+ mConfigurationController,
+ interruptionStateProvider,
+ mZenModeController,
+ mLockscreenUserManager,
+ mNotificationGroupManager);
mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
mBubbleController.setExpandListener(mBubbleExpandListener);
@@ -219,13 +218,14 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testRemoveBubble() {
mBubbleController.updateBubble(mRow.getEntry());
+ assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
assertTrue(mBubbleController.hasBubbles());
verify(mNotificationEntryManager).updateNotifications();
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
assertFalse(mStatusBarWindowController.getBubblesShowing());
- assertTrue(mRow.getEntry().isBubbleDismissed());
+ assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
verify(mNotificationEntryManager, times(2)).updateNotifications();
verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
}
@@ -236,10 +236,10 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
// Make it look like dismissed notif
- mRow.getEntry().setShowInShadeWhenBubble(false);
+ mBubbleData.getBubbleWithKey(mRow.getEntry().key).setShowInShadeWhenBubble(false);
// Now remove the bubble
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -255,15 +255,17 @@ public class BubbleControllerTest extends SysuiTestCase {
public void testDismissStack() {
mBubbleController.updateBubble(mRow.getEntry());
verify(mNotificationEntryManager, times(1)).updateNotifications();
+ assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
mBubbleController.updateBubble(mRow2.getEntry());
verify(mNotificationEntryManager, times(2)).updateNotifications();
+ assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mStatusBarWindowController.getBubblesShowing());
verify(mNotificationEntryManager, times(3)).updateNotifications();
- assertTrue(mRow.getEntry().isBubbleDismissed());
- assertTrue(mRow2.getEntry().isBubbleDismissed());
+ assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
+ assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
}
@Test
@@ -274,9 +276,9 @@ public class BubbleControllerTest extends SysuiTestCase {
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
- // We should have bubbles & their notifs should show in the shade
+ // We should have bubbles & their notifs should not be suppressed
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
assertFalse(mStatusBarWindowController.getBubbleExpanded());
// Expand the stack
@@ -286,8 +288,8 @@ public class BubbleControllerTest extends SysuiTestCase {
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
assertTrue(mStatusBarWindowController.getBubbleExpanded());
- // Make sure it's no longer in the shade
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ // Make sure the notif is suppressed
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
// Collapse
mBubbleController.collapseStack();
@@ -304,10 +306,11 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
- // We should have bubbles & their notifs should show in the shade
+ // We should have bubbles & their notifs should not be suppressed
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
- assertTrue(mRow2.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ mRow2.getEntry().key));
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
@@ -317,13 +320,13 @@ public class BubbleControllerTest extends SysuiTestCase {
// Last added is the one that is expanded
assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
- assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
// Switch which bubble is expanded
mBubbleController.selectBubble(mRow.getEntry().key);
- stackView.setExpandedBubble(mRow.getEntry());
+ stackView.setExpandedBubble(mRow.getEntry().key);
assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
// collapse for previous bubble
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
@@ -336,14 +339,37 @@ public class BubbleControllerTest extends SysuiTestCase {
}
@Test
- public void testExpansionRemovesShowInShade() {
+ public void testExpansionRemovesShowInShadeAndDot() {
+ // Mark it as a bubble and add it explicitly
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry());
+
+ // We should have bubbles & their notifs should not be suppressed
+ assertTrue(mBubbleController.hasBubbles());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+ // Expand
+ mBubbleController.expandStack();
+ assertTrue(mBubbleController.isStackExpanded());
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
+
+ // Notif is suppressed after expansion
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ // Notif shouldn't show dot after expansion
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+ }
+
+ @Test
+ public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
mEntryListener.onPendingEntryAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
- // We should have bubbles & their notifs should show in the shade
+ // We should have bubbles & their notifs should not be suppressed
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
// Expand
BubbleStackView stackView = mBubbleController.getStackView();
@@ -351,8 +377,19 @@ public class BubbleControllerTest extends SysuiTestCase {
assertTrue(mBubbleController.isStackExpanded());
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
- // No longer show shade in notif after expansion
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ // Notif is suppressed after expansion
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ // Notif shouldn't show dot after expansion
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+ // Send update
+ mEntryListener.onPreEntryUpdated(mRow.getEntry());
+
+ // Nothing should have changed
+ // Notif is suppressed after expansion
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ // Notif shouldn't show dot after expansion
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
}
@Test
@@ -373,7 +410,7 @@ public class BubbleControllerTest extends SysuiTestCase {
// Last added is the one that is expanded
assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
- assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
// Dismiss currently expanded
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
@@ -397,14 +434,16 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testAutoExpand_FailsNotForeground() {
assertFalse(mBubbleController.isStackExpanded());
+ setMetadataFlags(mRow.getEntry(),
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
- mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry());
// Expansion shouldn't change
verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
- mAutoExpandRow.getEntry().key);
+ mRow.getEntry().key);
assertFalse(mBubbleController.isStackExpanded());
// # of bubbles should change
@@ -413,91 +452,51 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testAutoExpand_SucceedsForeground() {
- final CountDownLatch latch = new CountDownLatch(1);
- BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- }
- };
- IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
- mContext.registerReceiver(receiver, filter);
-
- assertFalse(mBubbleController.isStackExpanded());
-
- // Make ourselves foreground
- Intent i = new Intent(mContext, BubblesTestActivity.class);
- i.setFlags(FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(i);
-
- try {
- latch.await(100, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ setMetadataFlags(mRow.getEntry(),
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
- mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry());
// Expansion should change
verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
- mAutoExpandRow.getEntry().key);
+ mRow.getEntry().key);
assertTrue(mBubbleController.isStackExpanded());
// # of bubbles should change
verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
- mContext.unregisterReceiver(receiver);
}
@Test
public void testSuppressNotif_FailsNotForeground() {
- // Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
- mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+ setMetadataFlags(mRow.getEntry(),
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, false /* enableFlag */);
- // Should show in shade because we weren't forground
- assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
+ // Add the suppress notif bubble
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry());
+ // Should not be suppressed because we weren't forground
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
// # of bubbles should change
verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
}
@Test
public void testSuppressNotif_SucceedsForeground() {
- final CountDownLatch latch = new CountDownLatch(1);
- BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- }
- };
- IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
- mContext.registerReceiver(receiver, filter);
-
- assertFalse(mBubbleController.isStackExpanded());
-
- // Make ourselves foreground
- Intent i = new Intent(mContext, BubblesTestActivity.class);
- i.setFlags(FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(i);
-
- try {
- latch.await(100, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ setMetadataFlags(mRow.getEntry(),
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
// Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
- mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry());
- // Should NOT show in shade because we were foreground
- assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
+ // Notif should be suppressed because we were foreground
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
// # of bubbles should change
verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
- mContext.unregisterReceiver(receiver);
}
@Test
@@ -516,7 +515,8 @@ public class BubbleControllerTest extends SysuiTestCase {
@Test
public void testMarkNewNotificationAsShowInShade() {
mEntryListener.onPendingEntryAdded(mRow.getEntry());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+ assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
}
@Test
@@ -584,7 +584,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
mRow.getEntry().key, REASON_CANCEL_ALL);
@@ -592,7 +592,7 @@ public class BubbleControllerTest extends SysuiTestCase {
// Intercept!
assertTrue(intercepted);
// Should update show in shade state
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
verify(mNotificationEntryManager, never()).performRemoveNotification(
any(), anyInt());
@@ -605,7 +605,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
mRow.getEntry().key, REASON_CANCEL);
@@ -613,7 +613,7 @@ public class BubbleControllerTest extends SysuiTestCase {
// Intercept!
assertTrue(intercepted);
// Should update show in shade state
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
verify(mNotificationEntryManager, never()).performRemoveNotification(
any(), anyInt());
@@ -626,7 +626,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
- assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
// Dismiss the bubble
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -646,22 +646,21 @@ public class BubbleControllerTest extends SysuiTestCase {
StatusBarWindowController statusBarWindowController, BubbleData data,
ConfigurationController configurationController,
NotificationInterruptionStateProvider interruptionStateProvider,
- ZenModeController zenModeController) {
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager lockscreenUserManager,
+ NotificationGroupManager groupManager) {
super(context, statusBarWindowController, data, Runnable::run,
- configurationController, interruptionStateProvider, zenModeController);
- }
-
- @Override
- public boolean shouldAutoBubbleForFlags(Context c, NotificationEntry entry) {
- return entry.notification.getNotification().getBubbleMetadata() != null;
+ configurationController, interruptionStateProvider, zenModeController,
+ lockscreenUserManager, groupManager);
}
}
- public static class TestableNotificationInterruptionStateProvider extends
+ static class TestableNotificationInterruptionStateProvider extends
NotificationInterruptionStateProvider {
- public TestableNotificationInterruptionStateProvider(Context context) {
- super(context);
+ TestableNotificationInterruptionStateProvider(Context context,
+ NotificationFilter filter, StatusBarStateController controller) {
+ super(context, filter, controller);
mUseHeadsUp = true;
}
}
@@ -676,4 +675,21 @@ public class BubbleControllerTest extends SysuiTestCase {
.setIntent(bubbleIntent)
.setIcon(Icon.createWithResource(mContext, R.drawable.android));
}
+
+ /**
+ * Sets the bubble metadata flags for this entry. These flags are normally set by
+ * NotificationManagerService when the notification is sent, however, these tests do not
+ * go through that path so we set them explicitly when testing.
+ */
+ private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
+ Notification.BubbleMetadata bubbleMetadata =
+ entry.notification.getNotification().getBubbleMetadata();
+ int flags = bubbleMetadata.getFlags();
+ if (enableFlag) {
+ flags |= flag;
+ } else {
+ flags &= ~flag;
+ }
+ bubbleMetadata.setFlags(flags);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 3eea853ef2bc..238cfd78bfaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -887,7 +887,7 @@ public class BubbleDataTest extends SysuiTestCase {
private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
setPostTime(entry, postTime);
- mBubbleData.notificationEntryUpdated(entry);
+ mBubbleData.notificationEntryUpdated(entry, /* suppressFlyout=*/ false);
}
private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
index 173237f7b311..a8961a85c4c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
@@ -18,7 +18,6 @@ package com.android.systemui.bubbles;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.verify;
@@ -46,6 +45,7 @@ import org.mockito.MockitoAnnotations;
public class BubbleFlyoutViewTest extends SysuiTestCase {
private BubbleFlyoutView mFlyout;
private TextView mFlyoutText;
+ private float[] mDotCenter = new float[2];
@Before
public void setUp() throws Exception {
@@ -53,20 +53,25 @@ public class BubbleFlyoutViewTest extends SysuiTestCase {
mFlyout = new BubbleFlyoutView(getContext());
mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+ mDotCenter[0] = 30;
+ mDotCenter[1] = 30;
}
@Test
public void testShowFlyout_isVisible() {
- mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
+ mFlyout.setupFlyoutStartingAsDot(
+ "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter);
+ mFlyout.setVisibility(View.VISIBLE);
+
assertEquals("Hello", mFlyoutText.getText());
assertEquals(View.VISIBLE, mFlyout.getVisibility());
- assertEquals(1f, mFlyoutText.getAlpha(), .01f);
}
@Test
public void testFlyoutHide_runsCallback() {
Runnable after = Mockito.mock(Runnable.class);
- mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, after);
+ mFlyout.setupFlyoutStartingAsDot(
+ "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter);
mFlyout.hideFlyout();
verify(after).run();
@@ -74,19 +79,16 @@ public class BubbleFlyoutViewTest extends SysuiTestCase {
@Test
public void testSetCollapsePercent() {
- mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
-
- float initialTranslationZ = mFlyout.getTranslationZ();
+ mFlyout.setupFlyoutStartingAsDot(
+ "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter);
+ mFlyout.setVisibility(View.VISIBLE);
mFlyout.setCollapsePercent(1f);
assertEquals(0f, mFlyoutText.getAlpha(), 0.01f);
assertNotSame(0f, mFlyoutText.getTranslationX()); // Should have moved to collapse.
- assertTrue(mFlyout.getTranslationZ() < initialTranslationZ); // Should be descending.
mFlyout.setCollapsePercent(0f);
assertEquals(1f, mFlyoutText.getAlpha(), 0.01f);
assertEquals(0f, mFlyoutText.getTranslationX());
- assertEquals(initialTranslationZ, mFlyout.getTranslationZ());
-
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index 57a6aae744f0..04cf4bb4c94b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.bubbles;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.os.Bundle;
import android.service.notification.NotificationListenerService.Ranking;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -31,6 +32,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import org.junit.Before;
import org.junit.Test;
@@ -41,13 +43,14 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotificationEntryTest extends SysuiTestCase {
+public class BubbleTest extends SysuiTestCase {
@Mock
private StatusBarNotification mStatusBarNotification;
@Mock
private Notification mNotif;
private NotificationEntry mEntry;
+ private Bubble mBubble;
private Bundle mExtras;
@Before
@@ -56,11 +59,12 @@ public class NotificationEntryTest extends SysuiTestCase {
when(mStatusBarNotification.getKey()).thenReturn("key");
when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
-
+ when(mStatusBarNotification.getUser()).thenReturn(new UserHandle(0));
mExtras = new Bundle();
mNotif.extras = mExtras;
mEntry = NotificationEntry.buildForTest(mStatusBarNotification);
+ mBubble = new Bubble(mContext, mEntry);
}
@Test
@@ -79,7 +83,7 @@ public class NotificationEntryTest extends SysuiTestCase {
final String msg = "Hello there!";
doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
- assertEquals(msg, mEntry.getUpdateMessage(mContext));
+ assertEquals(msg, mBubble.getUpdateMessage(mContext));
}
@Test
@@ -90,7 +94,7 @@ public class NotificationEntryTest extends SysuiTestCase {
mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
// Should be big text, not the small text.
- assertEquals(msg, mEntry.getUpdateMessage(mContext));
+ assertEquals(msg, mBubble.getUpdateMessage(mContext));
}
@Test
@@ -98,7 +102,7 @@ public class NotificationEntryTest extends SysuiTestCase {
doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
// Media notifs don't get update messages.
- assertNull(mEntry.getUpdateMessage(mContext));
+ assertNull(mBubble.getUpdateMessage(mContext));
}
@Test
@@ -113,7 +117,7 @@ public class NotificationEntryTest extends SysuiTestCase {
"Really? I prefer them that way."});
// Should be the last one only.
- assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext));
+ assertEquals("Really? I prefer them that way.", mBubble.getUpdateMessage(mContext));
}
@Test
@@ -128,6 +132,6 @@ public class NotificationEntryTest extends SysuiTestCase {
"Oh, hello!", 0, "Mady").toBundle()});
// Should be the last one only.
- assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext));
+ assertEquals("Mady: Oh, hello!", mBubble.getUpdateMessage(mContext));
}
}
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
index b324235106c2..1fbb44346a84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.Mockito.verify;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -45,14 +46,18 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
private int mDisplayWidth = 500;
private int mDisplayHeight = 1000;
+ private int mExpandedViewPadding = 10;
+ private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
+ private float mLauncherGridDiff = 30f;
@Spy
private ExpandedAnimationController mExpandedController =
new ExpandedAnimationController(
new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
- 0 /* expandedViewPadding */);
+ mExpandedViewPadding, mOrientation);
+
private int mStackOffset;
- private float mBubblePadding;
+ private float mBubblePaddingTop;
private float mBubbleSize;
private PointF mExpansionPoint;
@@ -65,7 +70,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mExpansionPoint = new PointF(100, 100);
}
@@ -138,7 +143,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
assertEquals(500f, draggedBubble.getTranslationX(), 1f);
assertEquals(500f, draggedBubble.getTranslationY(), 1f);
- // Snap it back and make sure it made it back correctly.
mLayout.removeView(draggedBubble);
waitForLayoutMessageQueue();
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -174,7 +178,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- assertEquals(mBubblePadding, mViews.get(1).getTranslationX(), 1f);
+ assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
}
@Test
@@ -256,8 +260,8 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
* @return Bubble left x from left edge of screen.
*/
public float getBubbleLeft(int index) {
- float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
- return getRowLeft() + bubbleLeftFromRowLeft;
+ final float bubbleLeft = index * (mBubbleSize + getSpaceBetweenBubbles());
+ return getRowLeft() + bubbleLeft;
}
private float getRowLeft() {
@@ -265,16 +269,29 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
return 0;
}
int bubbleCount = mLayout.getChildCount();
+ final float totalBubbleWidth = bubbleCount * mBubbleSize;
+ final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+ final float rowWidth = totalGapWidth + totalBubbleWidth;
- // Width calculations.
- double bubble = bubbleCount * mBubbleSize;
- float gap = (bubbleCount - 1) * mBubblePadding;
- float row = gap + (float) bubble;
+ final float centerScreen = mDisplayWidth / 2f;
+ final float halfRow = rowWidth / 2f;
+ final float rowLeft = centerScreen - halfRow;
+
+ return rowLeft;
+ }
+
+ /**
+ * @return Space between bubbles in row above expanded view.
+ */
+ private float getSpaceBetweenBubbles() {
+ final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+ final float maxRowWidth = mDisplayWidth - rowMargins;
- float halfRow = row / 2f;
- float centerScreen = mDisplayWidth / 2;
- float rowLeftFromScreenLeft = centerScreen - halfRow;
+ final float totalBubbleWidth = mMaxBubbles * mBubbleSize;
+ final float totalGapWidth = maxRowWidth - totalBubbleWidth;
- return rowLeftFromScreenLeft;
+ final int gapCount = mMaxBubbles - 1;
+ final float gapWidth = totalGapWidth / gapCount;
+ return gapWidth;
}
}
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
index f8b32c213109..86554b1d71aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -156,8 +156,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
});
// Set end listeners for both x and y.
- mLayout.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
- mLayout.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
+ mTestableController.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
+ mTestableController.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
// Animate x, and wait for it to finish.
mTestableController.animationForChildAtIndex(0)
@@ -190,7 +190,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
});
// Set the end listener.
- mLayout.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+ mTestableController.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
// Animate x, and wait for it to finish.
mTestableController.animationForChildAtIndex(0)
@@ -205,7 +205,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
mTestableController.animationForChildAtIndex(0)
.translationX(1000)
.start();
- mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+ mTestableController.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
xLatch.await(1, TimeUnit.SECONDS);
// Make sure the end listener was not called.
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
index f633f3996d13..a5f2e8b3db37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -68,7 +68,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
@Mock
private DisplayCutout mCutout;
- private int mMaxBubbles;
+ protected int mMaxBubbles;
@Before
public void setUp() throws Exception {
@@ -195,14 +195,13 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
private void setTestEndActionForProperty(
Runnable action, DynamicAnimation.ViewProperty property) {
final Runnable realEndAction = mEndActionForProperty.get(property);
-
- setEndActionForProperty(() -> {
+ mLayout.mEndActionForProperty.put(property, () -> {
if (realEndAction != null) {
realEndAction.run();
}
action.run();
- }, property);
+ });
}
/** PhysicsPropertyAnimator that posts its animations to the main thread. */
@@ -219,6 +218,11 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
property, view, value, startVel, startDelay, stiffness, dampingRatio,
afterCallbacks));
}
+
+ @Override
+ protected void startPathAnimation() {
+ mMainThreadHandler.post(super::startPathAnimation);
+ }
}
/**
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
index 31a7d5a45b68..d79128ca5c78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -339,10 +339,10 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
@Override
protected void springFirstBubbleWithStackFollowing(DynamicAnimation.ViewProperty property,
- SpringForce spring, float vel, float finalPosition) {
+ SpringForce spring, float vel, float finalPosition, Runnable... after) {
mMainThreadHandler.post(() ->
super.springFirstBubbleWithStackFollowing(
- property, spring, vel, finalPosition));
+ property, spring, vel, finalPosition, after));
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
new file mode 100644
index 000000000000..b044595e5a8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -0,0 +1,589 @@
+/*
+ * 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 static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.dreams.IDreamManager;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the interruption state provider which understands whether the system & notification
+ * is in a state allowing a particular notification to hun, pulse, or bubble.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class NotificationInterruptionStateProviderTest extends SysuiTestCase {
+
+ @Mock
+ PowerManager mPowerManager;
+ @Mock
+ IDreamManager mDreamManager;
+ @Mock
+ AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+ @Mock
+ NotificationFilter mNotificationFilter;
+ @Mock
+ StatusBarStateController mStatusBarStateController;
+ @Mock
+ NotificationPresenter mPresenter;
+ @Mock
+ HeadsUpManager mHeadsUpManager;
+ @Mock
+ NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+
+ private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mNotifInterruptionStateProvider =
+ new TestableNotificationInterruptionStateProvider(mContext,
+ mPowerManager,
+ mDreamManager,
+ mAmbientDisplayConfiguration,
+ mNotificationFilter,
+ mStatusBarStateController);
+
+ mNotifInterruptionStateProvider.setUpWithPresenter(
+ mPresenter,
+ mHeadsUpManager,
+ mHeadsUpSuppressor);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills group suppression check.
+ */
+ private void ensureStateForAlertCommon() {
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#canAlertAwakeCommon(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills launch fullscreen check.
+ */
+ private void ensureStateForAlertAwakeCommon() {
+ when(mPresenter.isDeviceInVrMode()).thenReturn(false);
+ when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills importance & DND checks.
+ */
+ private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
+ ensureStateForAlertCommon();
+ ensureStateForAlertAwakeCommon();
+
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills importance & DND checks.
+ */
+ private void ensureStateForHeadsUpWhenDozing() {
+ ensureStateForAlertCommon();
+
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills importance & bubble checks.
+ */
+ private void ensureStateForBubbleUp() {
+ ensureStateForAlertCommon();
+ ensureStateForAlertAwakeCommon();
+ }
+
+ /**
+ * Ensure that the disabled state is set correctly.
+ */
+ @Test
+ public void testDisableNotificationAlerts() {
+ // Enabled by default
+ assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+
+ // Disable alerts
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+ assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isTrue();
+
+ // Enable alerts
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(false);
+ assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+ }
+
+ /**
+ * Ensure that the disabled alert state effects whether HUNs are enabled.
+ */
+ @Test
+ public void testHunSettingsChange_enabled_butAlertsDisabled() {
+ // Set up but without a mock change observer
+ mNotifInterruptionStateProvider.setUpWithPresenter(
+ mPresenter,
+ mHeadsUpManager,
+ mHeadsUpSuppressor);
+
+ // HUNs enabled by default
+ assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isTrue();
+
+ // Set alerts disabled
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+
+ // No more HUNs
+ assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+ }
+
+ /**
+ * Alerts can happen.
+ */
+ @Test
+ public void testCanAlertCommon_true() {
+ ensureStateForAlertCommon();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue();
+ }
+
+ /**
+ * Filtered out notifications don't alert.
+ */
+ @Test
+ public void testCanAlertCommon_false_filteredOut() {
+ ensureStateForAlertCommon();
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+ }
+
+ /**
+ * Grouped notifications have different alerting behaviours, sometimes the alert for a
+ * grouped notification may be suppressed {@link android.app.Notification#GROUP_ALERT_CHILDREN}.
+ */
+ @Test
+ public void testCanAlertCommon_false_suppressedForGroups() {
+ ensureStateForAlertCommon();
+
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setGroup("a")
+ .setGroupSummary(true)
+ .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+ UserHandle.of(0), null, 0);
+ NotificationEntry entry = NotificationEntry.buildForTest(sbn);
+ entry.importance = IMPORTANCE_DEFAULT;
+
+ assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+ }
+
+ /**
+ * HUNs while dozing can happen.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_true() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+ }
+
+ /**
+ * Ambient display can show HUNs for new notifications, this may be disabled.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_pulseDisabled() {
+ ensureStateForHeadsUpWhenDozing();
+ when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the device is not in ambient display or sleeping then we don't HUN.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_notDozing() {
+ ensureStateForHeadsUpWhenDozing();
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * In DND ambient effects can be suppressed
+ * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ entry.suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
+ * get to pulse.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_lessImportant() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_LOW);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * Heads up can happen.
+ */
+ @Test
+ public void testShouldHeadsUp_true() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+ }
+
+ /**
+ * Heads up notifications can be disabled in general.
+ */
+ @Test
+ public void testShouldHeadsUp_false_noHunsAllowed() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ // Set alerts disabled, this should cause heads up to be false
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+ assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the device is dozing, we don't show as heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_dozing() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification is a bubble, and the user is not on AOD / lockscreen, then
+ * the bubble is shown rather than the heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_bubble() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ // Bubble bit only applies to interruption when we're in the shade
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(createBubble())).isFalse();
+ }
+
+ /**
+ * If we're not allowed to alert in general, we shouldn't be shown as heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ // Make canAlertCommon false by saying it's filtered out
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * In DND HUN peek effects can be suppressed
+ * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
+ */
+ @Test
+ public void testShouldHeadsUp_false_suppressPeek() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_HIGH} don't get
+ * to show as a heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_lessImportant() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the device is not in use then we shouldn't be shown as heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+ // Device is not in use if screen is not on
+ when(mPowerManager.isScreenOn()).thenReturn(false);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+ // Also not in use if screen is on but we're showing screen saver / "dreaming"
+ when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If something wants to suppress this heads up, then it shouldn't be shown as a heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_suppressed() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ verify(mHeadsUpSuppressor).canHeadsUp(any(), any());
+ }
+
+ /**
+ * On screen alerts don't happen when the device is in VR Mode.
+ */
+ @Test
+ public void testCanAlertAwakeCommon__false_vrMode() {
+ ensureStateForAlertAwakeCommon();
+ when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+ }
+
+ /**
+ * On screen alerts don't happen when the notification is snoozed.
+ */
+ @Test
+ public void testCanAlertAwakeCommon_false_snoozedPackage() {
+ ensureStateForAlertAwakeCommon();
+ when(mHeadsUpManager.isSnoozed(any())).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+ }
+
+ /**
+ * On screen alerts don't happen when that package has just launched fullscreen.
+ */
+ @Test
+ public void testCanAlertAwakeCommon_false_justLaunchedFullscreen() {
+ ensureStateForAlertAwakeCommon();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ entry.notifyFullScreenIntentLaunched();
+
+ assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+ }
+
+ /**
+ * Bubbles can happen.
+ */
+ @Test
+ public void testShouldBubbleUp_true() {
+ ensureStateForBubbleUp();
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
+ }
+
+ /**
+ * If the notification doesn't have permission to bubble, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_notAllowedToBubble() {
+ ensureStateForBubbleUp();
+
+ NotificationEntry entry = createBubble();
+ entry.canBubble = false;
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification isn't a bubble, it should definitely not show as a bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_notABubble() {
+ ensureStateForBubbleUp();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.canBubble = true;
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification doesn't have bubble metadata, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_invalidMetadata() {
+ ensureStateForBubbleUp();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.canBubble = true;
+ entry.notification.getNotification().flags |= FLAG_BUBBLE;
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification can't heads up in general, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_alertAwakeCommonFalse() {
+ ensureStateForBubbleUp();
+
+ // Make alert common return false by pretending we're in VR mode
+ when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+ }
+
+ /**
+ * If the notification can't heads up in general, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_alertCommonFalse() {
+ ensureStateForBubbleUp();
+
+ // Make canAlertCommon false by saying it's filtered out
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+ }
+
+ private NotificationEntry createBubble() {
+ Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+ .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
+ .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+ .build();
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .setBubbleMetadata(data)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+ UserHandle.of(0), null, 0);
+ NotificationEntry entry = NotificationEntry.buildForTest(sbn);
+ entry.notification.getNotification().flags |= FLAG_BUBBLE;
+ entry.importance = IMPORTANCE_HIGH;
+ entry.canBubble = true;
+ return entry;
+ }
+
+ private NotificationEntry createNotification(int importance) {
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+ UserHandle.of(0), null, 0);
+ NotificationEntry entry = NotificationEntry.buildForTest(sbn);
+ entry.importance = importance;
+ return entry;
+ }
+
+ /**
+ * Testable class overriding constructor.
+ */
+ public class TestableNotificationInterruptionStateProvider extends
+ NotificationInterruptionStateProvider {
+
+ TestableNotificationInterruptionStateProvider(Context context,
+ PowerManager powerManager, IDreamManager dreamManager,
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ NotificationFilter notificationFilter,
+ StatusBarStateController statusBarStateController) {
+ super(context, powerManager, dreamManager, ambientDisplayConfiguration,
+ notificationFilter,
+ statusBarStateController);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 54d8688857fa..388cf582237b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -41,7 +41,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -110,8 +110,9 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
mock(StatusBarStateControllerImpl.class), mEntryManager,
- () -> mShadeController, new BubbleData(mContext),
+ () -> mShadeController,
mock(KeyguardBypassController.class),
+ mock(BubbleController.class),
mock(DynamicPrivacyController.class));
Dependency.get(InitController.class).executePostInitTasks();
mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 97ad47ec3f0c..c5d4019252ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -69,6 +69,7 @@ public class ScrimControllerTest extends SysuiTestCase {
private SynchronousScrimController mScrimController;
private ScrimView mScrimBehind;
private ScrimView mScrimInFront;
+ private ScrimView mScrimForBubble;
private ScrimState mScrimState;
private float mScrimBehindAlpha;
private GradientColors mScrimInFrontColor;
@@ -84,6 +85,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void setup() {
mScrimBehind = spy(new ScrimView(getContext()));
mScrimInFront = new ScrimView(getContext());
+ mScrimForBubble = new ScrimView(getContext());
mWakeLock = mock(WakeLock.class);
mAlarmManager = mock(AlarmManager.class);
mAlwaysOnEnabled = true;
@@ -92,6 +94,7 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
mScrimController = new SynchronousScrimController(mScrimBehind, mScrimInFront,
+ mScrimForBubble,
(scrimState, scrimBehindAlpha, scrimInFrontColor) -> {
mScrimState = scrimState;
mScrimBehindAlpha = scrimBehindAlpha;
@@ -114,21 +117,28 @@ public class ScrimControllerTest extends SysuiTestCase {
public void transitionToKeyguard() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
- assertScrimTint(mScrimBehind, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
public void transitionToAod_withRegularWallpaper() {
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible with tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
@@ -136,14 +146,18 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be transparent
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
// Pulsing notification should conserve AOD wallpaper.
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -152,11 +166,14 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible with tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
@@ -166,11 +183,14 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.finishAnimationsImmediately();
mScrimController.setHasBackdrop(true);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible with tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
@@ -179,27 +199,32 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.setAodFrontScrimAlpha(0.5f);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
// ... but that it does take effect once we enter the AOD state.
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be semi-transparent
- // Back scrim should be visible
- assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
// ... and that if we set it while we're in AOD, it does take immediate effect.
mScrimController.setAodFrontScrimAlpha(1f);
- assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(OPAQUE /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
// ... and make sure we recall the previous front scrim alpha even if we transition away
// for a bit.
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(OPAQUE /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
// ... and alpha updates should be completely ignored if always_on is off.
// Passing it forward would mess up the wake-up transition.
@@ -223,20 +248,28 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent, but tinted
// Back scrim should be semi-transparent so the user can see the wallpaper
// Pulse callback should have been invoked
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
mScrimController.setWakeLockScreenSensorActive(true);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -245,8 +278,13 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
- assertScrimTint(mScrimBehind, false /* tinted */);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
}
@Test
@@ -255,8 +293,12 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
- assertScrimTint(mScrimBehind, false /* tinted */);
+ assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
}
@Test
@@ -264,15 +306,19 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setPanelExpansion(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be transparent
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
- assertScrimTint(mScrimBehind, false /* tinted */);
- assertScrimTint(mScrimInFront, false /* tinted */);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
// Back scrim should be visible after start dragging
mScrimController.setPanelExpansion(0.5f);
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -280,12 +326,19 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
mScrimController.finishAnimationsImmediately();
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
+
// Front scrim should be transparent
- Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
+ Assert.assertEquals(ScrimController.TRANSPARENT,
mScrimInFront.getViewAlpha(), 0.0f);
// Back scrim should be visible
Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
mScrimBehind.getViewAlpha(), 0.0f);
+ // Bubble scrim should be visible
+ Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+ mScrimBehind.getViewAlpha(), 0.0f);
}
@Test
@@ -354,16 +407,22 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setPanelExpansion(0f);
mScrimController.finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
- // Immediately tinted after the transition starts
- assertScrimTint(mScrimInFront, true /* tinted */);
- assertScrimTint(mScrimBehind, true /* tinted */);
+
+ // Immediately tinted black after the transition starts
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ true /* bubble */);
+
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be transparent
- // Neither scrims should be tinted anymore after the animation.
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
- assertScrimTint(mScrimInFront, false /* tinted */);
- assertScrimTint(mScrimBehind, false /* tinted */);
+
+ // All scrims should be transparent at the end of fade transition.
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* behind */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
}
@Test
@@ -378,9 +437,11 @@ public class ScrimControllerTest extends SysuiTestCase {
// Front scrim should be black in the middle of the transition
Assert.assertTrue("Scrim should be visible during transition. Alpha: "
+ mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
- assertScrimTint(mScrimInFront, true /* tinted */);
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ true /* bubble */);
Assert.assertSame("Scrim should be visible during transition.",
- mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
+ mScrimVisibility, OPAQUE);
}
});
mScrimController.finishAnimationsImmediately();
@@ -588,11 +649,15 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setKeyguardOccluded(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* behind */,
+ TRANSPARENT /* bubble */);
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* behind */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -600,11 +665,15 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* behind */,
+ TRANSPARENT /* bubble */);
mScrimController.setKeyguardOccluded(true);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* behind */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -643,33 +712,68 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimInFront.getDefaultFocusHighlightEnabled());
Assert.assertFalse("Scrim shouldn't have focus highlight",
mScrimBehind.getDefaultFocusHighlightEnabled());
+ Assert.assertFalse("Scrim shouldn't have focus highlight",
+ mScrimForBubble.getDefaultFocusHighlightEnabled());
}
- private void assertScrimTint(ScrimView scrimView, boolean tinted) {
- final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
- final String name = scrimView == mScrimInFront ? "front" : "back";
+ private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
+ Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ + " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
+ + Integer.toHexString(mScrimInFront.getTint()),
+ front, mScrimInFront.getTint() != Color.TRANSPARENT);
+
Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
- +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
- tinted, viewIsTinted);
+ + " with scrim: " + getScrimName(mScrimBehind) + " and tint: "
+ + Integer.toHexString(mScrimBehind.getTint()),
+ behind, mScrimBehind.getTint() != Color.TRANSPARENT);
+
+ Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ + " with scrim: " + getScrimName(mScrimForBubble) + " and tint: "
+ + Integer.toHexString(mScrimForBubble.getTint()),
+ bubble, mScrimForBubble.getTint() != Color.TRANSPARENT);
+ }
+
+ private String getScrimName(ScrimView scrim) {
+ if (scrim == mScrimInFront) {
+ return "front";
+ } else if (scrim == mScrimBehind) {
+ return "back";
+ } else if (scrim == mScrimForBubble) {
+ return "bubble";
+ }
+ return "unknown_scrim";
}
- private void assertScrimVisibility(int inFront, int behind) {
- boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
- boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
- Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
- + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
- Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
- + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
+ private void assertScrimAlpha(int front, int behind, int bubble) {
+ // Check single scrim visibility.
+ Assert.assertEquals("Unexpected front scrim alpha: "
+ + mScrimInFront.getViewAlpha(),
+ front != TRANSPARENT /* expected */,
+ mScrimInFront.getViewAlpha() > TRANSPARENT /* actual */);
+
+ Assert.assertEquals("Unexpected back scrim alpha: "
+ + mScrimBehind.getViewAlpha(),
+ behind != TRANSPARENT /* expected */,
+ mScrimBehind.getViewAlpha() > TRANSPARENT /* actual */);
+
+ Assert.assertEquals(
+ "Unexpected bubble scrim alpha: "
+ + mScrimForBubble.getViewAlpha(), /* message */
+ bubble != TRANSPARENT /* expected */,
+ mScrimForBubble.getViewAlpha() > TRANSPARENT /* actual */);
+ // Check combined scrim visibility.
final int visibility;
- if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
- visibility = VISIBILITY_FULLY_OPAQUE;
- } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
- visibility = VISIBILITY_SEMI_TRANSPARENT;
+ if (front == OPAQUE || behind == OPAQUE || bubble == OPAQUE) {
+ visibility = OPAQUE;
+ } else if (front > TRANSPARENT || behind > TRANSPARENT || bubble > TRANSPARENT) {
+ visibility = SEMI_TRANSPARENT;
} else {
- visibility = VISIBILITY_FULLY_TRANSPARENT;
+ visibility = TRANSPARENT;
}
- Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
+ Assert.assertEquals("Invalid visibility.",
+ visibility /* expected */,
+ mScrimVisibility);
}
/**
@@ -681,11 +785,12 @@ public class ScrimControllerTest extends SysuiTestCase {
boolean mOnPreDrawCalled;
SynchronousScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+ ScrimView scrimForBubble,
TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
- super(scrimBehind, scrimInFront, scrimStateListener, scrimVisibleListener,
- dozeParameters, alarmManager, keyguardMonitor);
+ super(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
+ scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
}
@Override
@@ -696,13 +801,14 @@ public class ScrimControllerTest extends SysuiTestCase {
void finishAnimationsImmediately() {
boolean[] animationFinished = {false};
- setOnAnimationFinished(()-> animationFinished[0] = true);
+ setOnAnimationFinished(() -> animationFinished[0] = true);
// Execute code that will trigger animations.
onPreDraw();
// Force finish all animations.
mLooper.processAllMessages();
endAnimation(mScrimBehind, TAG_KEY_ANIM);
endAnimation(mScrimInFront, TAG_KEY_ANIM);
+ endAnimation(mScrimForBubble, TAG_KEY_ANIM);
if (!animationFinished[0]) {
throw new IllegalStateException("Animation never finished");
@@ -740,6 +846,7 @@ public class ScrimControllerTest extends SysuiTestCase {
/**
* Do not wait for a frame since we're in a test environment.
+ *
* @param callback What to execute.
*/
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 06d76ebcff28..5a6f27dcfaae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -153,8 +153,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification();
bubbleSbn.getNotification().contentIntent = mContentIntent;
bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
- // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
- mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
mActiveNotifications = new ArrayList<>();
mActiveNotifications.add(mNotificationRow.getEntry());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7fbf18367f61..a1afd1d9de29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -208,7 +208,8 @@ public class StatusBarTest extends SysuiTestCase {
mNotificationInterruptionStateProvider =
new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
- mDreamManager, mAmbientDisplayConfiguration);
+ mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
+ mStatusBarStateController);
mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
mNotificationInterruptionStateProvider);
mDependency.injectMockDependency(NavigationBarController.class);
@@ -870,8 +871,11 @@ public class StatusBarTest extends SysuiTestCase {
Context context,
PowerManager powerManager,
IDreamManager dreamManager,
- AmbientDisplayConfiguration ambientDisplayConfiguration) {
- super(context, powerManager, dreamManager, ambientDisplayConfiguration);
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ NotificationFilter filter,
+ StatusBarStateController controller) {
+ super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
+ controller);
mUseHeadsUp = true;
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e67ccc42f1e2..2a2dc3d3cdb6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1102,7 +1102,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
- final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext);
+ final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler);
dataConnectionStats.startMonitoring();
mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
@@ -4551,7 +4551,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
return false;
}
- setLockdownTracker(new LockdownVpnTracker(mContext, mNMS, this, vpn, profile));
+ setLockdownTracker(new LockdownVpnTracker(mContext, this, mHandler, vpn, profile));
} else {
setLockdownTracker(null);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7bc2e6d647be..8eb5d9bcb5b8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2318,9 +2318,8 @@ public final class ActiveServices {
return true;
}
+ /** @return {@code true} if the restart is scheduled. */
private final boolean scheduleServiceRestartLocked(ServiceRecord r, boolean allowCancel) {
- boolean canceled = false;
-
if (mAm.mAtmInternal.isShuttingDown()) {
Slog.w(TAG, "Not scheduling restart of crashed service " + r.shortInstanceName
+ " - system is shutting down");
@@ -2337,10 +2336,12 @@ public final class ActiveServices {
final long now = SystemClock.uptimeMillis();
+ final String reason;
if ((r.serviceInfo.applicationInfo.flags
&ApplicationInfo.FLAG_PERSISTENT) == 0) {
long minDuration = mAm.mConstants.SERVICE_RESTART_DURATION;
long resetTime = mAm.mConstants.SERVICE_RESET_RUN_DURATION;
+ boolean canceled = false;
// Any delivered but not yet finished starts should be put back
// on the pending list.
@@ -2367,6 +2368,17 @@ public final class ActiveServices {
r.deliveredStarts.clear();
}
+ if (allowCancel) {
+ final boolean shouldStop = r.canStopIfKilled(canceled);
+ if (shouldStop && !r.hasAutoCreateConnections()) {
+ // Nothing to restart.
+ return false;
+ }
+ reason = (r.startRequested && !shouldStop) ? "start-requested" : "connection";
+ } else {
+ reason = "always";
+ }
+
r.totalRestartCount++;
if (r.restartDelay == 0) {
r.restartCount++;
@@ -2418,6 +2430,7 @@ public final class ActiveServices {
r.restartCount = 0;
r.restartDelay = 0;
r.nextRestartTime = now;
+ reason = "persistent";
}
if (!mRestartingServices.contains(r)) {
@@ -2432,11 +2445,11 @@ public final class ActiveServices {
mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
Slog.w(TAG, "Scheduling restart of crashed service "
- + r.shortInstanceName + " in " + r.restartDelay + "ms");
+ + r.shortInstanceName + " in " + r.restartDelay + "ms for " + reason);
EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
r.userId, r.shortInstanceName, r.restartDelay);
- return canceled;
+ return true;
}
final void performServiceRestartLocked(ServiceRecord r) {
@@ -3651,22 +3664,21 @@ public final class ActiveServices {
|| !mAm.mUserController.isUserRunning(sr.userId, 0)) {
bringDownServiceLocked(sr);
} else {
- boolean canceled = scheduleServiceRestartLocked(sr, true);
+ final boolean scheduled = scheduleServiceRestartLocked(sr, true /* allowCancel */);
// Should the service remain running? Note that in the
// extreme case of so many attempts to deliver a command
// that it failed we also will stop it here.
- if (sr.startRequested && (sr.stopIfKilled || canceled)) {
- if (sr.pendingStarts.size() == 0) {
- sr.startRequested = false;
- if (sr.tracker != null) {
- sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
- SystemClock.uptimeMillis());
- }
- if (!sr.hasAutoCreateConnections()) {
- // Whoops, no reason to restart!
- bringDownServiceLocked(sr);
- }
+ if (!scheduled) {
+ bringDownServiceLocked(sr);
+ } else if (sr.canStopIfKilled(false /* isStartCanceled */)) {
+ // Update to stopped state because the explicit start is gone. The service is
+ // scheduled to restart for other reason (e.g. connections) so we don't bring
+ // down it.
+ sr.startRequested = false;
+ if (sr.tracker != null) {
+ sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
+ SystemClock.uptimeMillis());
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 775a7dd44f68..3dc33f844092 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4644,7 +4644,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
boolean didSomething = mProcessList.killPackageProcessesLocked(packageName, appId, userId,
- ProcessList.INVALID_ADJ, callerWillRestart, true /* allowRestart */, doit,
+ ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
evenPersistent, true /* setRemoved */,
packageName == null ? ("stop user " + userId) : ("stop " + packageName));
@@ -8285,8 +8285,13 @@ public class ActivityManagerService extends IActivityManager.Stub
if (shareDescription != null) {
triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
}
- // Send broadcast to shell to trigger bugreport using Bugreport API
- mContext.sendBroadcast(triggerShellBugreport);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // Send broadcast to shell to trigger bugreport using Bugreport API
+ mContext.sendBroadcast(triggerShellBugreport);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
} else {
SystemProperties.set("dumpstate.options", type);
SystemProperties.set("ctl.start", "bugreport");
@@ -13919,9 +13924,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// Remove published content providers.
for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(i);
- final boolean always = app.bad || !allowRestart;
- boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
- if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
+ final boolean alwaysRemove = app.bad || !allowRestart;
+ final boolean inLaunching = removeDyingProviderLocked(app, cpr, alwaysRemove);
+ if (!alwaysRemove && inLaunching && cpr.hasConnectionOrHandle()) {
// We left the provider in the launching list, need to
// restart it.
restart = true;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8619ad5d3357..8c038aaab0b5 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -19,9 +19,7 @@ package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import android.app.INotificationManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -33,7 +31,6 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -623,6 +620,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
}
+ /**
+ * @return {@code true} if the killed service which was started by {@link Context#startService}
+ * has no reason to start again. Note this condition doesn't consider the bindings.
+ */
+ boolean canStopIfKilled(boolean isStartCanceled) {
+ return startRequested && (stopIfKilled || isStartCanceled) && pendingStarts.isEmpty();
+ }
+
void updateHasBindingWhitelistingBgActivityStarts() {
boolean hasWhitelistingBinding = false;
for (int conni = connections.size() - 1; conni >= 0; conni--) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index cc8e3f097450..d11b6f247f21 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -31,9 +31,11 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE_LOCATION;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.app.AppOpsManager._NUM_OP;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import android.Manifest;
import android.annotation.NonNull;
@@ -49,6 +51,7 @@ import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
+import android.app.AsyncNotedAppOp;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -58,6 +61,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioAttributes;
@@ -69,6 +73,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -86,6 +91,7 @@ import android.util.AtomicFile;
import android.util.KeyValueListParser;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -96,6 +102,7 @@ import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
@@ -181,6 +188,8 @@ public class AppOpsService extends IAppOpsService.Stub {
OP_CAMERA,
};
+ private static final int MAX_UNFORWARED_OPS = 10;
+
Context mContext;
final AtomicFile mFile;
final Handler mHandler;
@@ -188,6 +197,29 @@ public class AppOpsService extends IAppOpsService.Stub {
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
= new AppOpsManagerInternalImpl();
+ /**
+ * Registered callbacks, called from {@link #noteAsyncOp}.
+ *
+ * <p>(package name, uid) -> callbacks
+ *
+ * @see #getAsyncNotedOpsKey(String, int)
+ */
+ @GuardedBy("this")
+ private final ArrayMap<Pair<String, Integer>, RemoteCallbackList<IAppOpsAsyncNotedCallback>>
+ mAsyncOpWatchers = new ArrayMap<>();
+
+ /**
+ * Async note-ops collected from {@link #noteAsyncOp} that have not been delivered to a
+ * callback yet.
+ *
+ * <p>(package name, uid) -> list&lt;ops&gt;
+ *
+ * @see #getAsyncNotedOpsKey(String, int)
+ */
+ @GuardedBy("this")
+ private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
+ mUnforwardedAsyncNotedOps = new ArrayMap<>();
+
boolean mWriteScheduled;
boolean mFastWriteScheduled;
final Runnable mWriteRunner = new Runnable() {
@@ -2098,6 +2130,141 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@Override
+ public void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+ String message) {
+ Preconditions.checkNotNull(message);
+ Preconditions.checkNotNull(packageName);
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ verifyIncomingUid(uid);
+ verifyIncomingOp(opCode);
+
+ int callingUid = Binder.getCallingUid();
+ long now = System.currentTimeMillis();
+
+ if (callingPackageName != null) {
+ verifyAndGetIsPrivileged(callingUid, callingPackageName);
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+ callingPackageName, message, now);
+ final boolean[] wasNoteForwarded = {false};
+
+ if (callbacks != null) {
+ callbacks.broadcast((cb) -> {
+ try {
+ cb.opNoted(asyncNotedOp);
+ wasNoteForwarded[0] = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "Could not forward noteOp of " + opCode + " to " + packageName
+ + "/" + uid, e);
+ }
+ });
+ }
+
+ if (!wasNoteForwarded[0]) {
+ ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
+ if (unforwardedOps == null) {
+ unforwardedOps = new ArrayList<>(1);
+ mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+ }
+
+ unforwardedOps.add(asyncNotedOp);
+ if (unforwardedOps.size() > MAX_UNFORWARED_OPS) {
+ unforwardedOps.remove(0);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Compute a key to be used in {@link #mAsyncOpWatchers} and {@link #mUnforwardedAsyncNotedOps}
+ *
+ * @param packageName The package name of the app
+ * @param uid The uid of the app
+ *
+ * @return They key uniquely identifying the app
+ */
+ private @NonNull Pair<String, Integer> getAsyncNotedOpsKey(@NonNull String packageName,
+ int uid) {
+ return new Pair<>(packageName, uid);
+ }
+
+ @Override
+ public void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(callback);
+
+ int uid = Binder.getCallingUid();
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ synchronized (this) {
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks == null) {
+ callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
+ @Override
+ public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
+ synchronized (AppOpsService.this) {
+ if (getRegisteredCallbackCount() == 0) {
+ mAsyncOpWatchers.remove(key);
+ }
+ }
+ }
+ };
+ mAsyncOpWatchers.put(key, callbacks);
+ }
+
+ callbacks.register(callback);
+ }
+ }
+
+ @Override
+ public void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(callback);
+
+ int uid = Binder.getCallingUid();
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ synchronized (this) {
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks != null) {
+ callbacks.unregister(callback);
+ if (callbacks.getRegisteredCallbackCount() == 0) {
+ mAsyncOpWatchers.remove(key);
+ }
+ }
+ }
+ }
+
+ @Override
+ public List<AsyncNotedAppOp> extractAsyncOps(String packageName) {
+ Preconditions.checkNotNull(packageName);
+
+ int uid = Binder.getCallingUid();
+
+ verifyAndGetIsPrivileged(uid, packageName);
+
+ synchronized (this) {
+ return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
+ }
+ }
+
+ @Override
public int startOperation(IBinder token, int code, int uid, String packageName,
boolean startIfModeDefault) {
verifyIncomingUid(uid);
@@ -2340,6 +2507,25 @@ public class AppOpsService extends IAppOpsService.Stub {
return AppOpsManager.permissionToOpCode(permission);
}
+ @Override
+ public boolean shouldCollectNotes(int opCode) {
+ Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
+
+ String perm = AppOpsManager.opToPermission(opCode);
+ if (perm == null) {
+ return false;
+ }
+
+ PermissionInfo permInfo;
+ try {
+ permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ return permInfo.getProtection() == PROTECTION_DANGEROUS;
+ }
+
void finishOperationLocked(Op op, boolean finishNested) {
final int opCode = op.op;
final int uid = op.uidState.uid;
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 4990ea136246..27f11ffa60ce 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -21,6 +21,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
@@ -39,15 +41,19 @@ public class DataConnectionStats extends BroadcastReceiver {
private final Context mContext;
private final IBatteryStats mBatteryStats;
+ private final Handler mListenerHandler;
+ private final PhoneStateListener mPhoneStateListener;
private IccCardConstants.State mSimState = IccCardConstants.State.READY;
private SignalStrength mSignalStrength;
private ServiceState mServiceState;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
- public DataConnectionStats(Context context) {
+ public DataConnectionStats(Context context, Handler listenerHandler) {
mContext = context;
mBatteryStats = BatteryStatsService.getService();
+ mListenerHandler = listenerHandler;
+ mPhoneStateListener = new PhoneStateListenerImpl(listenerHandler.getLooper());
}
public void startMonitoring() {
@@ -63,7 +69,7 @@ public class DataConnectionStats extends BroadcastReceiver {
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
- mContext.registerReceiver(this, filter);
+ mContext.registerReceiver(this, filter, null /* broadcastPermission */, mListenerHandler);
}
@Override
@@ -129,7 +135,11 @@ public class DataConnectionStats extends BroadcastReceiver {
&& mServiceState.getState() != ServiceState.STATE_POWER_OFF;
}
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ private class PhoneStateListenerImpl extends PhoneStateListener {
+ PhoneStateListenerImpl(Looper looper) {
+ super(looper);
+ }
+
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
mSignalStrength = signalStrength;
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 67a018a90f94..77fbe41ebb88 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -19,6 +19,8 @@ package com.android.server.net;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.provider.Settings.ACTION_VPN_SETTINGS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -32,7 +34,7 @@ import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
-import android.os.INetworkManagementService;
+import android.os.Handler;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
@@ -63,19 +65,18 @@ public class LockdownVpnTracker {
private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
- private static final int ROOT_UID = 0;
+ @NonNull private final Context mContext;
+ @NonNull private final ConnectivityService mConnService;
+ @NonNull private final Handler mHandler;
+ @NonNull private final Vpn mVpn;
+ @NonNull private final VpnProfile mProfile;
- private final Context mContext;
- private final INetworkManagementService mNetService;
- private final ConnectivityService mConnService;
- private final Vpn mVpn;
- private final VpnProfile mProfile;
+ @NonNull private final Object mStateLock = new Object();
- private final Object mStateLock = new Object();
-
- private final PendingIntent mConfigIntent;
- private final PendingIntent mResetIntent;
+ @NonNull private final PendingIntent mConfigIntent;
+ @NonNull private final PendingIntent mResetIntent;
+ @Nullable
private String mAcceptedEgressIface;
private int mErrorCount;
@@ -84,11 +85,14 @@ public class LockdownVpnTracker {
return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
}
- public LockdownVpnTracker(Context context, INetworkManagementService netService,
- ConnectivityService connService, Vpn vpn, VpnProfile profile) {
+ public LockdownVpnTracker(@NonNull Context context,
+ @NonNull ConnectivityService connService,
+ @NonNull Handler handler,
+ @NonNull Vpn vpn,
+ @NonNull VpnProfile profile) {
mContext = Preconditions.checkNotNull(context);
- mNetService = Preconditions.checkNotNull(netService);
mConnService = Preconditions.checkNotNull(connService);
+ mHandler = Preconditions.checkNotNull(handler);
mVpn = Preconditions.checkNotNull(vpn);
mProfile = Preconditions.checkNotNull(profile);
@@ -198,7 +202,7 @@ public class LockdownVpnTracker {
mVpn.setLockdown(true);
final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
- mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
+ mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, mHandler);
handleStateChangedLocked();
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index b4a80994cc65..976a0c663101 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -217,6 +217,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.SomeArgs;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -3281,7 +3282,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
- long timeoutMillis, String callingPackage) {
+ long networkTypeMask, long timeoutMillis, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
// We can only override when carrier told us about plans
@@ -3299,11 +3300,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(),
NETPOLICY_OVERRIDE_ENABLED, 1) != 0;
if (overrideEnabled || overrideValue == 0) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
- overrideMask, overrideValue, subId));
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = subId;
+ args.arg2 = overrideMask;
+ args.arg3 = overrideValue;
+ args.arg4 = networkTypeMask;
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args));
if (timeoutMillis > 0) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
- overrideMask, 0, subId), timeoutMillis);
+ args.arg3 = 0;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args),
+ timeoutMillis);
}
}
}
@@ -4439,10 +4445,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
- int overrideMask, int overrideValue) {
+ int overrideMask, int overrideValue, long networkTypeMask) {
if (listener != null) {
try {
- listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
+ listener.onSubscriptionOverride(subId, overrideMask, overrideValue,
+ networkTypeMask);
} catch (RemoteException ignored) {
}
}
@@ -4543,13 +4550,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
return true;
}
case MSG_SUBSCRIPTION_OVERRIDE: {
- final int overrideMask = msg.arg1;
- final int overrideValue = msg.arg2;
- final int subId = (int) msg.obj;
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int subId = (int) args.arg1;
+ final int overrideMask = (int) args.arg2;
+ final int overrideValue = (int) args.arg3;
+ final long networkTypeMask = (long) args.arg4;
final int length = mListeners.beginBroadcast();
for (int i = 0; i < length; i++) {
final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
- dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
+ dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue,
+ networkTypeMask);
}
mListeners.finishBroadcast();
return true;
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index 358bdb90f6d3..e59bf1642ba8 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -41,10 +41,9 @@ public class BubbleExtractor implements NotificationSignalExtractor {
if (DBG) Slog.d(TAG, "missing config");
return null;
}
- boolean userWantsBubbles = mConfig.bubblesEnabled(record.sbn.getUser());
boolean appCanShowBubble =
mConfig.areBubblesAllowed(record.sbn.getPackageName(), record.sbn.getUid());
- if (!userWantsBubbles || !appCanShowBubble) {
+ if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
record.setAllowBubble(false);
} else {
if (record.getChannel() != null) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 30b2245ec24e..f04d4590f6ad 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -94,6 +94,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.PreferencesHelper.DEFAULT_ALLOW_BUBBLE;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -1391,7 +1392,9 @@ public class NotificationManagerService extends SystemService {
private final class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_BADGING_URI
= Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
- private final Uri NOTIFICATION_BUBBLES_URI
+ private final Uri NOTIFICATION_BUBBLES_URI_GLOBAL
+ = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_BUBBLES);
+ private final Uri NOTIFICATION_BUBBLES_URI_SECURE
= Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
private final Uri NOTIFICATION_LIGHT_PULSE_URI
= Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
@@ -1410,7 +1413,9 @@ public class NotificationManagerService extends SystemService {
false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
+ resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_GLOBAL,
+ false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_SECURE,
false, this, UserHandle.USER_ALL);
update(null);
}
@@ -1437,9 +1442,41 @@ public class NotificationManagerService extends SystemService {
if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
mPreferencesHelper.updateBadgingEnabled();
}
- if (uri == null || NOTIFICATION_BUBBLES_URI.equals(uri)) {
+ // In QPR we moved the setting to Global rather than Secure so that the setting
+ // applied to work profiles. Unfortunately we need to maintain both to pass CTS without
+ // a change to CTS outside of a normal letter release.
+ if (uri == null || NOTIFICATION_BUBBLES_URI_GLOBAL.equals(uri)) {
+ syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_GLOBAL);
mPreferencesHelper.updateBubblesEnabled();
}
+ if (NOTIFICATION_BUBBLES_URI_SECURE.equals(uri)) {
+ syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_SECURE);
+ }
+ }
+
+ private void syncBubbleSettings(ContentResolver resolver, Uri settingToFollow) {
+ boolean followSecureSetting = settingToFollow.equals(NOTIFICATION_BUBBLES_URI_SECURE);
+
+ int secureSettingValue = Settings.Secure.getInt(resolver,
+ Settings.Secure.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+ int globalSettingValue = Settings.Global.getInt(resolver,
+ Settings.Global.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+
+ if (globalSettingValue == secureSettingValue) {
+ return;
+ }
+
+ if (followSecureSetting) {
+ // Global => secure
+ Settings.Global.putInt(resolver,
+ Settings.Global.NOTIFICATION_BUBBLES,
+ secureSettingValue);
+ } else {
+ // Secure => Global
+ Settings.Secure.putInt(resolver,
+ Settings.Secure.NOTIFICATION_BADGING,
+ globalSettingValue);
+ }
}
}
@@ -4958,47 +4995,112 @@ public class NotificationManagerService extends SystemService {
} else {
notification.flags &= ~FLAG_BUBBLE;
}
+ // Is the app in the foreground?
+ final boolean appIsForeground =
+ mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+ Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+ if (!appIsForeground && metadata != null) {
+ // Remove any flags that only work when foregrounded
+ int flags = metadata.getFlags();
+ flags &= ~Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+ flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+ metadata.setFlags(flags);
+ }
}
/**
- * @return whether the provided notification record is allowed to be represented as a bubble.
+ * @return whether the provided notification record is allowed to be represented as a bubble,
+ * accounting for user choice & policy.
*/
private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
NotificationRecord oldRecord) {
Notification notification = r.getNotification();
- Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
- boolean intentCanBubble = metadata != null
- && canLaunchInActivityView(getContext(), metadata.getIntent(), pkg);
+ if (!canBubble(r, pkg, userId)) {
+ // no log: canBubble has its own
+ return false;
+ }
- // Does the app want to bubble & is able to bubble
- boolean canBubble = intentCanBubble
- && mPreferencesHelper.areBubblesAllowed(pkg, userId)
- && mPreferencesHelper.bubblesEnabled(r.sbn.getUser())
- && r.getChannel().canBubble()
- && !mActivityManager.isLowRamDevice();
+ if (mActivityManager.isLowRamDevice()) {
+ logBubbleError(r.getKey(), "low ram device");
+ return false;
+ }
- // Is the app in the foreground?
- final boolean appIsForeground =
- mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+ if (mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND) {
+ // If the app is foreground it always gets to bubble
+ return true;
+ }
+
+ if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
+ // This is an update to an active bubble
+ return true;
+ }
+
+ // At this point the bubble must fulfill communication policy
- // Is the notification something we'd allow to bubble?
- // A call with a foreground service + person
+ // Communication always needs a person
ArrayList<Person> peopleList = notification.extras != null
? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
: null;
- boolean isForegroundCall = CATEGORY_CALL.equals(notification.category)
- && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
- // OR message style (which always has a person) with any remote input
- Class<? extends Notification.Style> style = notification.getNotificationStyle();
- boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
- boolean notificationAppropriateToBubble =
- (isMessageStyle && hasValidRemoteInput(notification))
- || (peopleList != null && !peopleList.isEmpty() && isForegroundCall);
+ // Message style requires a person & it's not included in the list
+ boolean isMessageStyle = Notification.MessagingStyle.class.equals(
+ notification.getNotificationStyle());
+ if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
+ logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+ + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+ return false;
+ }
- // OR something that was previously a bubble & still exists
- boolean bubbleUpdate = oldRecord != null
- && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0;
- return canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate);
+ // Communication is a message or a call
+ boolean isCall = CATEGORY_CALL.equals(notification.category);
+ boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+ if (isMessageStyle) {
+ if (hasValidRemoteInput(notification)) {
+ return true;
+ }
+ logBubbleError(r.getKey(), "messages require valid remote input");
+ return false;
+ } else if (isCall) {
+ if (hasForegroundService) {
+ return true;
+ }
+ logBubbleError(r.getKey(), "calls require foreground service");
+ return false;
+ }
+ logBubbleError(r.getKey(), "if not foreground, must be "
+ + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+ return false;
+ }
+
+ /**
+ * @return whether the user has enabled the provided notification to bubble, does not account
+ * for policy.
+ */
+ private boolean canBubble(NotificationRecord r, String pkg, int userId) {
+ Notification notification = r.getNotification();
+ Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+ if (metadata == null) {
+ // no log: no need to inform dev if they didn't attach bubble metadata
+ return false;
+ }
+ if (!canLaunchInActivityView(getContext(), metadata.getIntent(), pkg)) {
+ // no log: method has the failure log
+ return false;
+ }
+ if (!mPreferencesHelper.bubblesEnabled()) {
+ logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
+ return false;
+ }
+ if (!mPreferencesHelper.areBubblesAllowed(pkg, userId)) {
+ logBubbleError(r.getKey(),
+ "bubbles for package: " + pkg + " disabled for user: " + userId);
+ return false;
+ }
+ if (!r.getChannel().canBubble()) {
+ logBubbleError(r.getKey(),
+ "bubbles for channel " + r.getChannel().getId() + " disabled");
+ return false;
+ }
+ return true;
}
private boolean hasValidRemoteInput(Notification n) {
@@ -5017,6 +5119,11 @@ public class NotificationManagerService extends SystemService {
return false;
}
+ private void logBubbleError(String key, String failureMessage) {
+ if (DBG) {
+ Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
+ }
+ }
/**
* Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
*
@@ -5383,12 +5490,26 @@ public class NotificationManagerService extends SystemService {
return;
}
+ // Bubbled children get to stick around if the summary was manually cancelled
+ // (user removed) from systemui.
+ FlagChecker childrenFlagChecker = null;
+ if (mReason == REASON_CANCEL
+ || mReason == REASON_CLICK
+ || mReason == REASON_CANCEL_ALL) {
+ childrenFlagChecker = (flags) -> {
+ if ((flags & FLAG_BUBBLE) != 0) {
+ return false;
+ }
+ return true;
+ };
+ }
+
// Cancel the notification.
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(
r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
- mSendDelete, null);
+ mSendDelete, childrenFlagChecker);
updateLightsLocked();
} else {
// No notification was found, assume that it is snoozed and cancel it.
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d580bd6f0a11..082b08debe9f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -104,7 +104,7 @@ public class PreferencesHelper implements RankingConfig {
@VisibleForTesting
static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
private static final boolean DEFAULT_SHOW_BADGE = true;
- private static final boolean DEFAULT_ALLOW_BUBBLE = true;
+ static final boolean DEFAULT_ALLOW_BUBBLE = true;
private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false;
@@ -134,7 +134,7 @@ public class PreferencesHelper implements RankingConfig {
private final ZenModeHelper mZenModeHelper;
private SparseBooleanArray mBadgingEnabled;
- private SparseBooleanArray mBubblesEnabled;
+ private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE;
private boolean mAreChannelsBypassingDnd;
private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
@@ -1840,40 +1840,19 @@ public class PreferencesHelper implements RankingConfig {
}
public void updateBubblesEnabled() {
- if (mBubblesEnabled == null) {
- mBubblesEnabled = new SparseBooleanArray();
- }
- boolean changed = false;
- // update the cached values
- for (int index = 0; index < mBubblesEnabled.size(); index++) {
- int userId = mBubblesEnabled.keyAt(index);
- final boolean oldValue = mBubblesEnabled.get(userId);
- final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_BUBBLES,
- DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
- mBubblesEnabled.put(userId, newValue);
- changed |= oldValue != newValue;
- }
- if (changed) {
+ final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_BUBBLES,
+ DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1;
+ if (newValue != mBubblesEnabled) {
+ mBubblesEnabled = newValue;
updateConfig();
}
}
- public boolean bubblesEnabled(UserHandle userHandle) {
- int userId = userHandle.getIdentifier();
- if (userId == UserHandle.USER_ALL) {
- return false;
- }
- if (mBubblesEnabled.indexOfKey(userId) < 0) {
- mBubblesEnabled.put(userId,
- Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_BUBBLES,
- DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
- }
- return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
+ public boolean bubblesEnabled() {
+ return mBubblesEnabled;
}
-
public void updateBadgingEnabled() {
if (mBadgingEnabled == null) {
mBadgingEnabled = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 5de00e43a05d..7816f3619023 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -30,7 +30,7 @@ public interface RankingConfig {
boolean canShowBadge(String packageName, int uid);
boolean badgingEnabled(UserHandle userHandle);
boolean areBubblesAllowed(String packageName, int uid);
- boolean bubblesEnabled(UserHandle userHandle);
+ boolean bubblesEnabled();
boolean isGroupBlocked(String packageName, int uid, String groupId);
Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 5bd557df0616..b17036a2f9a2 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -4758,6 +4758,7 @@ class ActivityStack extends ConfigurationContainer {
task.cleanUpResourcesForDestroy();
}
+ final ActivityDisplay display = getDisplay();
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
// We only need to adjust focused stack if this stack is in focus and we are not in the
@@ -4766,11 +4767,11 @@ class ActivityStack extends ConfigurationContainer {
&& mRootActivityContainer.isTopDisplayFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
- getDisplay().moveHomeStackToFront(myReason);
+ display.moveHomeStackToFront(myReason);
}
}
if (isAttached()) {
- getDisplay().positionChildAtBottom(this);
+ display.positionChildAtBottom(this);
}
if (!isActivityTypeHome() || !isAttached()) {
remove();
@@ -4783,6 +4784,9 @@ class ActivityStack extends ConfigurationContainer {
if (inPinnedWindowingMode()) {
mService.getTaskChangeNotificationController().notifyActivityUnpinned();
}
+ if (display.isSingleTaskInstance()) {
+ mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+ }
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f2ca2ba82dfb..1bcf47b42c97 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5923,6 +5923,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return allUids.contains(uid);
}
+ void notifySingleTaskDisplayEmpty(int displayId) {
+ mTaskChangeNotificationController.notifySingleTaskDisplayEmpty(displayId);
+ }
+
final class H extends Handler {
static final int REPORT_TIME_TRACKER_MSG = 1;
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index c2c476741963..5e8831d47c12 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -57,6 +57,7 @@ class TaskChangeNotificationController {
private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 23;
private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 24;
+ private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 25;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -161,6 +162,10 @@ class TaskChangeNotificationController {
l.onSingleTaskDisplayDrawn(m.arg1);
};
+ private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
+ l.onSingleTaskDisplayEmpty(m.arg1);
+ };
+
private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
l.onTaskDisplayChanged(m.arg1, m.arg2);
};
@@ -251,6 +256,9 @@ class TaskChangeNotificationController {
case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
break;
+ case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
+ forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
+ break;
case NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyTaskDisplayChanged, msg);
break;
@@ -513,6 +521,17 @@ class TaskChangeNotificationController {
}
/**
+ * Notify listeners that the last task is removed from a single task display.
+ */
+ void notifySingleTaskDisplayEmpty(int displayId) {
+ final Message msg = mHandler.obtainMessage(
+ NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
+ displayId, 0 /* unused */);
+ forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
+ msg.sendToTarget();
+ }
+
+ /**
* Notify listeners that a task is reparented to another display.
*/
void notifyTaskDisplayChanged(int taskId, int newDisplayId) {
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 73bb579bd274..4e0434873944 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -201,13 +201,8 @@ sp<ISystemSuspend> getSuspendHal() {
sp<ISuspendControlService> getSuspendControl() {
static std::once_flag suspendControlFlag;
std::call_once(suspendControlFlag, [](){
- while(gSuspendControl == nullptr) {
- sp<IBinder> control =
- defaultServiceManager()->getService(String16("suspend_control"));
- if (control != nullptr) {
- gSuspendControl = interface_cast<ISuspendControlService>(control);
- }
- }
+ gSuspendControl = waitForService<ISuspendControlService>(String16("suspend_control"));
+ LOG_ALWAYS_FATAL_IF(gSuspendControl == nullptr);
});
return gSuspendControl;
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index ff0648999843..0f077f36faeb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -55,13 +55,14 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.AtomicFile;
import com.android.server.LocalServices;
-import com.android.server.pm.permission.PermissionManagerService;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.permission.PermissionSettings;
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.FileOutputStream;
@@ -78,7 +79,14 @@ public class PackageManagerSettingsTests {
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final String PACKAGE_NAME_1 = "com.android.app1";
public static final String TAG = "PackageManagerSettingsTests";
- protected final String PREFIX = "android.content.pm";
+
+ @Mock
+ PermissionSettings mPermissionSettings;
+
+ @Before
+ public void initializeMocks() {
+ MockitoAnnotations.initMocks(this);
+ }
/** make sure our initialized KeySetManagerService metadata matches packages.xml */
@Test
@@ -88,9 +96,7 @@ public class PackageManagerSettingsTests {
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
- Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
verifyKeySetMetaData(settings);
}
@@ -103,9 +109,7 @@ public class PackageManagerSettingsTests {
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
- Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
// write out, read back in and verify the same
@@ -116,13 +120,11 @@ public class PackageManagerSettingsTests {
@Test
public void testSettingsReadOld() {
- // Write the package files and make sure they're parsed properly the first time
+ // Write delegateshellthe package files and make sure they're parsed properly the first time
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
- Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -143,15 +145,12 @@ public class PackageManagerSettingsTests {
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
- Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
settings.writeLPr();
// Create Settings again to make it read from the new files
- settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -313,9 +312,7 @@ public class PackageManagerSettingsTests {
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
- Settings settings =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
// Enable/Disable a package
@@ -507,9 +504,8 @@ public class PackageManagerSettingsTests {
public void testUpdatePackageSetting03() {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
final Settings testSettings01 =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ new Settings(context.getFilesDir(), mPermissionSettings, lock);
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 =
@@ -625,9 +621,8 @@ public class PackageManagerSettingsTests {
public void testCreateNewSetting03() {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
- PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
final Settings testSettings01 =
- new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+ new Settings(context.getFilesDir(), mPermissionSettings, lock);
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 = Settings.createNewSetting(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 273a9e66e55b..7459c4b0610e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -86,7 +86,7 @@ public class BubbleExtractorTest extends UiServiceTestCase {
BubbleExtractor extractor = new BubbleExtractor();
extractor.setConfig(mConfig);
- when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+ when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
@@ -100,7 +100,7 @@ public class BubbleExtractorTest extends UiServiceTestCase {
BubbleExtractor extractor = new BubbleExtractor();
extractor.setConfig(mConfig);
- when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+ when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
@@ -114,7 +114,7 @@ public class BubbleExtractorTest extends UiServiceTestCase {
BubbleExtractor extractor = new BubbleExtractor();
extractor.setConfig(mConfig);
- when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+ when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
@@ -128,7 +128,7 @@ public class BubbleExtractorTest extends UiServiceTestCase {
BubbleExtractor extractor = new BubbleExtractor();
extractor.setConfig(mConfig);
- when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+ when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
@@ -142,7 +142,7 @@ public class BubbleExtractorTest extends UiServiceTestCase {
BubbleExtractor extractor = new BubbleExtractor();
extractor.setConfig(mConfig);
- when(mConfig.bubblesEnabled(mUser)).thenReturn(false);
+ when(mConfig.bubblesEnabled()).thenReturn(false);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
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 3ac7a79a1630..be638a9d9755 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
@@ -446,7 +447,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
boolean channelEnabled) {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.bubblesEnabled(any())).thenReturn(globalEnabled);
+ when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
@@ -465,14 +466,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
return sbn;
}
+
private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
String groupKey, boolean isSummary) {
+ return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+ String groupKey, boolean isSummary, boolean isBubble) {
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setGroup(groupKey)
.setGroupSummary(isSummary);
-
+ if (isBubble) {
+ nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
+ }
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
nb.build(), new UserHandle(mUid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
@@ -568,6 +577,52 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
}
+ private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
+ throws RemoteException {
+
+ // Notification that has bubble metadata
+ NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
+ "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+
+ // Make the package foreground so that we're allowed to be a bubble
+ when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
+ IMPORTANCE_FOREGROUND);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+ waitForIdle();
+
+ // Make sure we are a bubble
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
+
+ // Plain notification without bubble metadata
+ NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
+ "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
+ waitForIdle();
+
+ notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(2, notifsAfter.length);
+
+ // Summary notification for both of those
+ NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
+ "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+ if (summaryAutoCancel) {
+ nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
+ }
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nrSummary.sbn.getId(), nrSummary.sbn.getNotification(), nrSummary.sbn.getUserId());
+ waitForIdle();
+
+ notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(3, notifsAfter.length);
+
+ return nrSummary;
+ }
+
@Test
public void testCreateNotificationChannels_SingleChannel() throws Exception {
final NotificationChannel channel =
@@ -5318,4 +5373,161 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mUsageStats, times(5)).registerImageRemoved(PKG);
}
+
+ public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
+ throws Exception {
+ // Bubbles are allowed!
+ setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+ // Give it bubble metadata
+ Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+ .setSuppressNotification(true)
+ .setAutoExpandBubble(true).build();
+ // Give it a person
+ Person person = new Person.Builder()
+ .setName("bubblebot")
+ .build();
+ // It needs remote input to be bubble-able
+ RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+ PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+ Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+ inputIntent).addRemoteInput(remoteInput)
+ .build();
+ // Make it messaging style
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setBubbleMetadata(data)
+ .setStyle(new Notification.MessagingStyle(person)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello?",
+ SystemClock.currentThreadTimeMillis() - 300000, person)
+ .addMessage("Is it me you're looking for?",
+ SystemClock.currentThreadTimeMillis(), person)
+ )
+ .setActions(replyAction)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // Ensure we're not foreground
+ when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+ IMPORTANCE_VISIBLE);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // yes allowed, yes messaging, yes bubble
+ Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+ assertTrue(notif.isBubbleNotification());
+
+ // Our flags should have failed since we're not foreground
+ assertFalse(notif.getBubbleMetadata().getAutoExpandBubble());
+ assertFalse(notif.getBubbleMetadata().isNotificationSuppressed());
+ }
+
+ @Test
+ public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
+ throws RemoteException {
+ // Bubbles are allowed!
+ setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+ // Give it bubble metadata
+ Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+ .setSuppressNotification(true)
+ .setAutoExpandBubble(true).build();
+ // Give it a person
+ Person person = new Person.Builder()
+ .setName("bubblebot")
+ .build();
+ // It needs remote input to be bubble-able
+ RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+ PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+ Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+ inputIntent).addRemoteInput(remoteInput)
+ .build();
+ // Make it messaging style
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setBubbleMetadata(data)
+ .setStyle(new Notification.MessagingStyle(person)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello?",
+ SystemClock.currentThreadTimeMillis() - 300000, person)
+ .addMessage("Is it me you're looking for?",
+ SystemClock.currentThreadTimeMillis(), person)
+ )
+ .setActions(replyAction)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // Ensure we are in the foreground
+ when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+ IMPORTANCE_FOREGROUND);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // yes allowed, yes messaging, yes bubble
+ Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+ assertTrue(notif.isBubbleNotification());
+
+ // Our flags should have failed since we are foreground
+ assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
+ assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
+ }
+
+ @Test
+ public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
+ throws Exception {
+ // Bubbles are allowed!
+ setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+ NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+ true /* summaryAutoCancel */);
+
+ // Dismiss summary
+ final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+ true);
+ mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, nrSummary.sbn.getTag(),
+ nrSummary.sbn.getId(), nrSummary.getUserId(), nrSummary.getKey(),
+ NotificationStats.DISMISSAL_SHADE,
+ NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv);
+ waitForIdle();
+
+ // The bubble should still exist
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ }
+
+ @Test
+ public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
+ throws Exception {
+ // Bubbles are allowed!
+ setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+ NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+ true /* summaryAutoCancel */);
+
+ // Click summary
+ final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+ true);
+ mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+ nrSummary.getKey(), nv);
+ waitForIdle();
+
+ // The bubble should still exist
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 365cd80c88c7..80439cf66387 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -60,7 +60,9 @@ import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.Settings.Global;
import android.provider.Settings.Secure;
+
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
import android.util.ArrayMap;
@@ -154,8 +156,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
contentResolver.setFallbackToExisting(false);
Secure.putIntForUser(contentResolver,
Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID_N_MR1));
- Secure.putIntForUser(contentResolver,
- Secure.NOTIFICATION_BUBBLES, 1, UserHandle.getUserId(UID_N_MR1));
+ Global.putInt(contentResolver, Global.NOTIFICATION_BUBBLES, 1);
ContentProvider testContentProvider = mock(ContentProvider.class);
when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
@@ -1950,42 +1951,18 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testBubblesOverrideTrue() {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BUBBLES, 1,
- USER.getIdentifier());
+ Global.putInt(getContext().getContentResolver(),
+ Global.NOTIFICATION_BUBBLES, 1);
mHelper.updateBubblesEnabled(); // would be called by settings observer
- assertTrue(mHelper.bubblesEnabled(USER));
+ assertTrue(mHelper.bubblesEnabled());
}
@Test
public void testBubblesOverrideFalse() {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BUBBLES, 0,
- USER.getIdentifier());
- mHelper.updateBubblesEnabled(); // would be called by settings observer
- assertFalse(mHelper.bubblesEnabled(USER));
- }
-
- @Test
- public void testBubblesForUserAll() {
- try {
- mHelper.bubblesEnabled(UserHandle.ALL);
- } catch (Exception e) {
- fail("just don't throw");
- }
- }
-
- @Test
- public void testBubblesOverrideUserIsolation() {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BUBBLES, 0,
- USER.getIdentifier());
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BUBBLES, 1,
- USER2.getIdentifier());
+ Global.putInt(getContext().getContentResolver(),
+ Global.NOTIFICATION_BUBBLES, 0);
mHelper.updateBubblesEnabled(); // would be called by settings observer
- assertFalse(mHelper.bubblesEnabled(USER));
- assertTrue(mHelper.bubblesEnabled(USER2));
+ assertFalse(mHelper.bubblesEnabled());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 19fd93fee5f0..6e41118997ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -271,6 +271,54 @@ public class TaskStackChangedListenerTest {
waitForCallback(singleTaskDisplayDrawnLatch);
}
+ @Test
+ public void testSingleTaskDisplayEmpty() throws Exception {
+ final Instrumentation instrumentation = getInstrumentation();
+
+ final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+ final CountDownLatch activityViewDestroyedLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayEmptyLatch = new CountDownLatch(1);
+
+ registerTaskStackChangedListener(new TaskStackListener() {
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+ singleTaskDisplayDrawnLatch.countDown();
+ }
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId)
+ throws RemoteException {
+ singleTaskDisplayEmptyLatch.countDown();
+ }
+ });
+ final ActivityViewTestActivity activity =
+ (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+ final ActivityView activityView = activity.getActivityView();
+ activityView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ activityViewReadyLatch.countDown();
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ activityViewDestroyedLatch.countDown();
+ }
+ });
+ waitForCallback(activityViewReadyLatch);
+
+ final Context context = instrumentation.getContext();
+ Intent intent = new Intent(context, ActivityInActivityView.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ activityView.startActivity(intent);
+ waitForCallback(singleTaskDisplayDrawnLatch);
+ assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
+
+ activityView.release();
+ waitForCallback(activityViewDestroyedLatch);
+ waitForCallback(singleTaskDisplayEmptyLatch);
+ }
+
/**
* Starts the provided activity and returns the started instance.
*/
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 6b9b83ddb887..8b8c86be7b0a 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -16,6 +16,8 @@
package android.telecom;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -81,7 +83,7 @@ public class AudioState implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null) {
return false;
}
@@ -93,6 +95,7 @@ public class AudioState implements Parcelable {
getSupportedRouteMask() == state.getSupportedRouteMask();
}
+ @NonNull
@Override
public String toString() {
return String.format(Locale.US,
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 3a340053ace3..1c03d8089df7 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -44,6 +45,7 @@ public final class CallAttributes implements Parcelable {
this.mCallQuality = callQuality;
}
+ @NonNull
@Override
public String toString() {
return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
@@ -109,7 +111,7 @@ public final class CallAttributes implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o == null || !(o instanceof CallAttributes) || hashCode() != o.hashCode()) {
return false;
}
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index 10a04a9db260..028280c332e2 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -17,6 +17,8 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -252,6 +254,7 @@ public final class CallQuality implements Parcelable {
}
// Parcelable things
+ @NonNull
@Override
public String toString() {
return "CallQuality: {downlinkCallQualityLevel=" + mDownlinkCallQualityLevel
@@ -285,7 +288,7 @@ public final class CallQuality implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) {
return false;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 654b54d988cf..2aca206a8cc5 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2791,6 +2791,13 @@ public class CarrierConfigManager {
"opportunistic_network_data_switch_exit_hysteresis_time_long";
/**
+ * Controls time in milli seconds until DcTracker reevaluates 5G connection state.
+ * @hide
+ */
+ public static final String KEY_5G_WATCHDOG_TIME_MS_LONG =
+ "5g_watchdog_time_long";
+
+ /**
* Indicates zero or more emergency number prefix(es), because some carrier requires
* if users dial an emergency number address with a specific prefix, the combination of the
* prefix and the address is also a valid emergency number to dial. For example, an emergency
@@ -3565,6 +3572,8 @@ public class CarrierConfigManager {
sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG, 10000);
/* Default value is 3 seconds. */
sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 3000);
+ /* Default value is 1 hour. */
+ sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
sDefaults.putAll(Gps.getDefaults());
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
@@ -3799,7 +3808,7 @@ public class CarrierConfigManager {
* @see #getConfigForSubId
*/
@Nullable
- public PersistableBundle getConfigByComponentForSubId(String prefix, int subId) {
+ public PersistableBundle getConfigByComponentForSubId(@NonNull String prefix, int subId) {
PersistableBundle configs = getConfigForSubId(subId);
if (configs == null) {
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 950ae6cf5ed8..c13801887572 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -323,6 +323,7 @@ public final class CarrierRestrictionRules implements Parcelable {
}
};
+ @NonNull
@Override
public String toString() {
return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 3dd931898c18..407ced71a0e7 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -134,6 +135,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable {
return 0;
}
+ @NonNull
@Override
public String toString() {
return new StringBuilder().append(this.getClass().getName())
@@ -155,7 +157,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (!(o instanceof DataSpecificRegistrationInfo)) return false;
diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java
index ec9f078367b8..7994c1b05977 100644
--- a/telephony/java/android/telephony/LteVopsSupportInfo.java
+++ b/telephony/java/android/telephony/LteVopsSupportInfo.java
@@ -17,6 +17,8 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
@@ -94,7 +96,7 @@ public final class LteVopsSupportInfo implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (o == null || !(o instanceof LteVopsSupportInfo)) {
return false;
}
@@ -112,6 +114,7 @@ public final class LteVopsSupportInfo implements Parcelable {
/**
* @return string representation.
*/
+ @NonNull
@Override
public String toString() {
return ("LteVopsSupportInfo : "
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 2fae949cacb3..a76b8da09064 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -501,6 +501,7 @@ public final class NetworkRegistrationInfo implements Parcelable {
}
}
+ @NonNull
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationInfo{")
@@ -531,7 +532,7 @@ public final class NetworkRegistrationInfo implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (!(o instanceof NetworkRegistrationInfo)) {
diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java
index c35a485d613f..e6f107e28c98 100644
--- a/telephony/java/android/telephony/PhoneNumberRange.java
+++ b/telephony/java/android/telephony/PhoneNumberRange.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -104,7 +105,7 @@ public final class PhoneNumberRange implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PhoneNumberRange that = (PhoneNumberRange) o;
@@ -119,6 +120,7 @@ public final class PhoneNumberRange implements Parcelable {
return Objects.hash(mCountryCode, mPrefix, mLowerBound, mUpperBound);
}
+ @NonNull
@Override
public String toString() {
return "PhoneNumberRange{"
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index 5fb9bacb55b8..701a375a3039 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -17,6 +17,8 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
@@ -276,7 +278,7 @@ public final class PreciseCallState implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -294,6 +296,7 @@ public final class PreciseCallState implements Parcelable {
&& mPreciseDisconnectCause == other.mPreciseDisconnectCause);
}
+ @NonNull
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index cd4fbacd180e..90d443a6d8ee 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -177,7 +177,7 @@ public final class PreciseDataConnectionState implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (!(obj instanceof PreciseDataConnectionState)) {
return false;
@@ -191,6 +191,7 @@ public final class PreciseDataConnectionState implements Parcelable {
&& mState == other.mState;
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index bb2269fc4d00..58f285851375 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -580,7 +580,8 @@ public class SubscriptionInfo implements Parcelable {
try {
packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Unknown package: " + packageName, e);
+ Log.d("SubscriptionInfo", "canManageSubscription: Unknown package: " + packageName, e);
+ return false;
}
for (UiccAccessRule rule : allAccessRules) {
if (rule.getCarrierPrivilegeStatus(packageInfo)
@@ -612,7 +613,9 @@ public class SubscriptionInfo implements Parcelable {
*/
public @Nullable List<UiccAccessRule> getAllAccessRules() {
List<UiccAccessRule> merged = new ArrayList<>();
- if (mNativeAccessRules != null) merged.addAll(getAccessRules());
+ if (mNativeAccessRules != null) {
+ merged.addAll(getAccessRules());
+ }
if (mCarrierConfigAccessRules != null) {
merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5e47e498ee10..d702cf16c4d3 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -53,6 +53,7 @@ import android.os.ParcelUuid;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.TelephonyManager.NetworkType;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
import android.util.DisplayMetrics;
@@ -2454,10 +2455,51 @@ public class SubscriptionManager {
*/
public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
@DurationMillisLong long timeoutMillis) {
+ setSubscriptionOverrideUnmetered(subId, null, overrideUnmetered, timeoutMillis);
+ }
+
+ /**
+ * Temporarily override the billing relationship between a carrier and
+ * a specific subscriber to be considered unmetered for the given network
+ * types. This will be reflected to apps via
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
+ * This method is only accessible to the following narrow set of apps:
+ * <ul>
+ * <li>The carrier app for this subscriberId, as determined by
+ * {@link TelephonyManager#hasCarrierPrivileges()}.
+ * <li>The carrier app explicitly delegated access through
+ * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+ * </ul>
+ *
+ * @param subId the subscriber this override applies to.
+ * @param networkTypes all network types to set an override for. A null
+ * network type means to apply the override to all network types.
+ * Any unspecified network types will default to metered.
+ * @param overrideUnmetered set if the billing relationship should be
+ * considered unmetered.
+ * @param timeoutMillis the timeout after which the requested override will
+ * be automatically cleared, or {@code 0} to leave in the
+ * requested state until explicitly cleared, or the next reboot,
+ * whichever happens first.
+ * @throws SecurityException if the caller doesn't meet the requirements
+ * outlined above.
+ * {@hide}
+ */
+ public void setSubscriptionOverrideUnmetered(int subId,
+ @Nullable @NetworkType int[] networkTypes, boolean overrideUnmetered,
+ @DurationMillisLong long timeoutMillis) {
try {
+ long networkTypeMask = 0;
+ if (networkTypes != null) {
+ for (int networkType : networkTypes) {
+ networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+ }
+ } else {
+ networkTypeMask = ~0;
+ }
final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
- timeoutMillis, mContext.getOpPackageName());
+ networkTypeMask, timeoutMillis, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2489,10 +2531,52 @@ public class SubscriptionManager {
*/
public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
@DurationMillisLong long timeoutMillis) {
+ setSubscriptionOverrideCongested(subId, null, overrideCongested, timeoutMillis);
+ }
+
+ /**
+ * Temporarily override the billing relationship plan between a carrier and
+ * a specific subscriber to be considered congested. This will cause the
+ * device to delay certain network requests when possible, such as developer
+ * jobs that are willing to run in a flexible time window.
+ * <p>
+ * This method is only accessible to the following narrow set of apps:
+ * <ul>
+ * <li>The carrier app for this subscriberId, as determined by
+ * {@link TelephonyManager#hasCarrierPrivileges()}.
+ * <li>The carrier app explicitly delegated access through
+ * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+ * </ul>
+ *
+ * @param subId the subscriber this override applies to.
+ * @param networkTypes all network types to set an override for. A null
+ * network type means to apply the override to all network types.
+ * Any unspecified network types will default to not congested.
+ * @param overrideCongested set if the subscription should be considered
+ * congested.
+ * @param timeoutMillis the timeout after which the requested override will
+ * be automatically cleared, or {@code 0} to leave in the
+ * requested state until explicitly cleared, or the next reboot,
+ * whichever happens first.
+ * @throws SecurityException if the caller doesn't meet the requirements
+ * outlined above.
+ * @hide
+ */
+ public void setSubscriptionOverrideCongested(int subId,
+ @Nullable @NetworkType int[] networkTypes, boolean overrideCongested,
+ @DurationMillisLong long timeoutMillis) {
try {
+ long networkTypeMask = 0;
+ if (networkTypes != null) {
+ for (int networkType : networkTypes) {
+ networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+ }
+ } else {
+ networkTypeMask = ~0;
+ }
final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
- timeoutMillis, mContext.getOpPackageName());
+ networkTypeMask, timeoutMillis, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2606,7 +2690,7 @@ public class SubscriptionManager {
* @hide
*/
public boolean canManageSubscription(SubscriptionInfo info, String packageName) {
- if (info.getAllAccessRules() == null) {
+ if (info == null || info.getAllAccessRules() == null) {
return false;
}
PackageManager packageManager = mContext.getPackageManager();
@@ -2614,7 +2698,8 @@ public class SubscriptionManager {
try {
packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Unknown package: " + packageName, e);
+ logd("Unknown package: " + packageName);
+ return false;
}
for (UiccAccessRule rule : info.getAllAccessRules()) {
if (rule.getCarrierPrivilegeStatus(packageInfo)
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index e0838b375fdb..ec2050fb1a44 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -131,7 +131,7 @@ public final class SubscriptionPlan implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj instanceof SubscriptionPlan) {
final SubscriptionPlan other = (SubscriptionPlan) obj;
return Objects.equals(cycleRule, other.cycleRule)
diff --git a/telephony/java/android/telephony/TelephonyHistogram.java b/telephony/java/android/telephony/TelephonyHistogram.java
index 63bdac514477..b94cb60ffa00 100644
--- a/telephony/java/android/telephony/TelephonyHistogram.java
+++ b/telephony/java/android/telephony/TelephonyHistogram.java
@@ -15,13 +15,12 @@
*/
package android.telephony;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
/**
* Parcelable class to store Telephony histogram.
@@ -238,6 +237,8 @@ public final class TelephonyHistogram implements Parcelable {
}
}
+ @NonNull
+ @Override
public String toString() {
String basic = " Histogram id = " + mId + " Time(ms): min = " + mMinTimeMs + " max = "
+ mMaxTimeMs + " avg = " + mAverageTimeMs + " Count = " + mSampleCount;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 553bff26f78f..475563d37197 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2829,6 +2829,55 @@ public class TelephonyManager {
}
}
+ /**
+ * Returns the bitmask for a given technology (network type)
+ * @param networkType for which bitmask is returned
+ * @return the network type bitmask
+ * {@hide}
+ */
+ public static @NetworkTypeBitMask long getBitMaskForNetworkType(@NetworkType int networkType) {
+ switch(networkType) {
+ case NETWORK_TYPE_GSM:
+ return NETWORK_TYPE_BITMASK_GSM;
+ case NETWORK_TYPE_GPRS:
+ return NETWORK_TYPE_BITMASK_GPRS;
+ case NETWORK_TYPE_EDGE:
+ return NETWORK_TYPE_BITMASK_EDGE;
+ case NETWORK_TYPE_CDMA:
+ return NETWORK_TYPE_BITMASK_CDMA;
+ case NETWORK_TYPE_1xRTT:
+ return NETWORK_TYPE_BITMASK_1xRTT;
+ case NETWORK_TYPE_EVDO_0:
+ return NETWORK_TYPE_BITMASK_EVDO_0;
+ case NETWORK_TYPE_EVDO_A:
+ return NETWORK_TYPE_BITMASK_EVDO_A;
+ case NETWORK_TYPE_EVDO_B:
+ return NETWORK_TYPE_BITMASK_EVDO_B;
+ case NETWORK_TYPE_EHRPD:
+ return NETWORK_TYPE_BITMASK_EHRPD;
+ case NETWORK_TYPE_HSUPA:
+ return NETWORK_TYPE_BITMASK_HSUPA;
+ case NETWORK_TYPE_HSDPA:
+ return NETWORK_TYPE_BITMASK_HSDPA;
+ case NETWORK_TYPE_HSPA:
+ return NETWORK_TYPE_BITMASK_HSPA;
+ case NETWORK_TYPE_HSPAP:
+ return NETWORK_TYPE_BITMASK_HSPAP;
+ case NETWORK_TYPE_UMTS:
+ return NETWORK_TYPE_BITMASK_UMTS;
+ case NETWORK_TYPE_TD_SCDMA:
+ return NETWORK_TYPE_BITMASK_TD_SCDMA;
+ case NETWORK_TYPE_LTE:
+ return NETWORK_TYPE_BITMASK_LTE;
+ case NETWORK_TYPE_LTE_CA:
+ return NETWORK_TYPE_BITMASK_LTE_CA;
+ case NETWORK_TYPE_NR:
+ return NETWORK_TYPE_BITMASK_NR;
+ default:
+ return NETWORK_TYPE_BITMASK_UNKNOWN;
+ }
+ }
+
//
//
// SIM Card
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 37a4491141a0..811722f0bbff 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,6 +15,7 @@
*/
package android.telephony;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.pm.PackageInfo;
@@ -213,7 +214,7 @@ public final class UiccAccessRule implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -236,6 +237,7 @@ public final class UiccAccessRule implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index fb1da7b12556..a0e949aa7fc5 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -16,6 +16,8 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -179,7 +181,7 @@ public class UiccSlotInfo implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -210,6 +212,7 @@ public class UiccSlotInfo implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "UiccSlotInfo (mIsActive="
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 17699d70cb18..9170e88ce832 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -213,6 +213,7 @@ public final class DataCallResponse implements Parcelable {
*/
public int getMtu() { return mMtu; }
+ @NonNull
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
@@ -233,7 +234,7 @@ public final class DataCallResponse implements Parcelable {
}
@Override
- public boolean equals (Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (!(o instanceof DataCallResponse)) {
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index c53ade16cae4..0d79ec98fcbb 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -257,6 +257,7 @@ public final class DataProfile implements Parcelable {
return 0;
}
+ @NonNull
@Override
public String toString() {
return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType
@@ -303,7 +304,7 @@ public final class DataProfile implements Parcelable {
};
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DataProfile that = (DataProfile) o;
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index b27f7756151d..c348cff25052 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -16,6 +16,7 @@
package android.telephony.euicc;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -107,7 +108,7 @@ public final class EuiccNotification implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -132,6 +133,7 @@ public final class EuiccNotification implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "EuiccNotification (seq="
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index 89842aefc66e..d5a05ae2dbb8 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -16,6 +16,7 @@
package android.telephony.euicc;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,7 +29,6 @@ import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -204,7 +204,7 @@ public final class EuiccRulesAuthTable implements Parcelable {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
diff --git a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
index 9b72d58aa89f..79cdfef96300 100644
--- a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
+++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
@@ -185,6 +185,7 @@ public final class ImsCallForwardInfo implements Parcelable {
out.writeInt(mServiceClass);
}
+ @NonNull
@Override
public String toString() {
return super.toString() + ", Condition: " + mCondition
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 20879141bdb5..77ee20512d11 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -539,7 +539,7 @@ public final class ImsCallProfile implements Parcelable {
mMediaProfile = profile.mMediaProfile;
}
-
+ @NonNull
@Override
public String toString() {
return "{ serviceType=" + mServiceType
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 44595b5bc1fd..6f062f4185f2 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
@@ -176,6 +177,7 @@ public final class ImsConferenceState implements Parcelable {
return Call.STATE_ACTIVE;
}
+ @NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index 37b11eda6916..eb2ebcad2c1d 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -213,6 +213,7 @@ public final class ImsExternalCallState implements Parcelable {
return mIsHeld;
}
+ @NonNull
@Override
public String toString() {
return "ImsExternalCallState { mCallId = " + mCallId +
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index 20aba4d97849..1e0d9a786acc 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -17,6 +17,7 @@
package android.telephony.ims;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
@@ -1183,6 +1184,8 @@ public final class ImsReasonInfo implements Parcelable {
/**
* @return the string format of {@link ImsReasonInfo}
*/
+ @NonNull
+ @Override
public String toString() {
return "ImsReasonInfo :: {" + mCode + ", " + mExtraCode + ", " + mExtraMessage + "}";
}
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 32b4382829df..ec2ff6c58a40 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -570,6 +570,8 @@ public final class ImsSsData implements Parcelable {
return mCfInfo;
}
+ @NonNull
+ @Override
public String toString() {
return "[ImsSsData] " + "ServiceType: " + getServiceType()
+ " RequestType: " + getRequestType()
diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java
index 9912eced0543..be34f9db3e54 100644
--- a/telephony/java/android/telephony/ims/ImsSsInfo.java
+++ b/telephony/java/android/telephony/ims/ImsSsInfo.java
@@ -254,6 +254,7 @@ public final class ImsSsInfo implements Parcelable {
out.writeInt(mClirOutgoingState);
}
+ @NonNull
@Override
public String toString() {
return super.toString() + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled")
diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
index fd75a6b192d2..c1f059e1cc89 100644
--- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
+++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
@@ -194,6 +195,7 @@ public final class ImsStreamMediaProfile implements Parcelable {
mRttMode = profile.mRttMode;
}
+ @NonNull
@Override
public String toString() {
return "{ audioQuality=" + mAudioQuality +
diff --git a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
index 2e4288d0cf0a..16303685d0a3 100644
--- a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
+++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
@@ -17,6 +17,7 @@
package android.telephony.ims;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -67,6 +68,7 @@ public final class ImsSuppServiceNotification implements Parcelable {
history = in.createStringArray();
}
+ @NonNull
@Override
public String toString() {
return "{ notificationType=" + notificationType +
diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 80fc09e2b0cc..87a5094a95f3 100644
--- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
@@ -16,6 +16,7 @@
package android.telephony.ims.feature;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -98,6 +99,7 @@ public final class CapabilityChangeRequest implements Parcelable {
return radioTech;
}
+ @NonNull
@Override
public String toString() {
return "CapabilityPair{"
@@ -219,6 +221,7 @@ public final class CapabilityChangeRequest implements Parcelable {
}
}
+ @NonNull
@Override
public String toString() {
return "CapabilityChangeRequest{"
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 056a0abe7a29..20c191da0550 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -291,6 +291,7 @@ public class MmTelFeature extends ImsFeature {
return super.isCapable(capabilities);
}
+ @NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder("MmTel Capabilities - [");
diff --git a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
index 3b298bb82f8b..cd9ebbf38e35 100644
--- a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
+++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
@@ -16,6 +16,8 @@
package android.telephony.ims.stub;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -60,7 +62,7 @@ public final class ImsFeatureConfiguration implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -77,6 +79,7 @@ public final class ImsFeatureConfiguration implements Parcelable {
return result;
}
+ @NonNull
@Override
public String toString() {
return "{s=" + slotId + ", f=" + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "}";
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 52b51d243bd1..ac258cd40d65 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Intent;
@@ -381,7 +382,7 @@ public final class DownloadRequest implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null) {
return false;
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index e1113eba006f..668a6af08145 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -111,6 +111,9 @@ public class DctConstants {
public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
public static final int EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED = BASE + 51;
+ public static final int EVENT_5G_NETWORK_CHANGED = BASE + 52;
+ public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
+ public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
/***** Constants *****/
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index bfbe08ae396a..ed8f272874e1 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1020,6 +1020,7 @@ public class RollbackTest {
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
+ waitForUnavailableRollback(TestApp.A);
// Block the RollbackManager to make extra sure it will not be
// able to enable the rollback in time.
@@ -1028,6 +1029,10 @@ public class RollbackTest {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ // Give plenty of time for RollbackManager to unblock and attempt
+ // to make the rollback available before asserting that the
+ // rollback was not made available.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
assertThat(
getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
} finally {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f3c735cd8880..6ae639a6ee52 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -5692,7 +5692,6 @@ public class ConnectivityServiceTest {
String[] values = tcpBufferSizes.split(",");
String rmemValues = String.join(" ", values[0], values[1], values[2]);
String wmemValues = String.join(" ", values[3], values[4], values[5]);
- waitForIdle();
verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues);
reset(mMockNetd);
}
@@ -5700,18 +5699,32 @@ public class ConnectivityServiceTest {
@Test
public void testTcpBufferReset() throws Exception {
final String testTcpBufferSizes = "1,2,3,4,5,6";
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(networkRequest, networkCallback);
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
reset(mMockNetd);
// Switching default network updates TCP buffer sizes.
mCellNetworkAgent.connect(false);
+ networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
// Change link Properties should have updated tcp buffer size.
LinkProperties lp = new LinkProperties();
lp.setTcpBufferSizes(testTcpBufferSizes);
mCellNetworkAgent.sendLinkProperties(lp);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
verifyTcpBufferSizeChange(testTcpBufferSizes);
+
+ // Clean up.
+ mCellNetworkAgent.disconnect();
+ networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ networkCallback.assertNoCallback();
+ mCm.unregisterNetworkCallback(networkCallback);
}
@Test
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 6ce21216f753..6a03c73bc3f9 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -211,6 +211,7 @@ public class RttManager {
/** Draft 11mc version supported, including major and minor version. e.g, draft 4.3 is 43 */
public int mcVersion;
+ @NonNull
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
@@ -1130,6 +1131,7 @@ public class RttManager {
*/
public int preamble;
+ @NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java b/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
index 8262a7a36c56..95b2e77c5c1e 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
@@ -16,8 +16,8 @@
package android.net.wifi;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
-
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,7 +39,7 @@ public class WifiNetworkConnectionStatistics implements Parcelable {
public WifiNetworkConnectionStatistics() { }
-
+ @NonNull
@Override
public String toString() {
StringBuilder sbuf = new StringBuilder();
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
index 57dff9d761cb..a32bd547e1e2 100644
--- a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
@@ -16,6 +16,7 @@
package android.net.wifi.hotspot2;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.drawable.Icon;
@@ -219,7 +220,7 @@ public final class OsuProvider implements Parcelable {
}
@Override
- public boolean equals(Object thatObject) {
+ public boolean equals(@Nullable Object thatObject) {
if (this == thatObject) {
return true;
}
@@ -246,6 +247,7 @@ public final class OsuProvider implements Parcelable {
mServerUri, mNetworkAccessIdentifier, mMethodList);
}
+ @NonNull
@Override
public String toString() {
return "OsuProvider{mOsuSsid=" + mOsuSsid
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 70af03e56262..318efa61a110 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -17,6 +17,7 @@
package android.net.wifi.rtt;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.MacAddress;
import android.net.wifi.ScanResult;
@@ -245,7 +246,7 @@ public final class RangingRequest implements Parcelable {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index e6ae48344afc..64dfc3499aaf 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -21,6 +21,7 @@ import static android.net.wifi.ScanResult.InformationElement.EID_VHT_CAPABILITIE
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.MacAddress;
import android.net.wifi.ScanResult;
@@ -443,7 +444,7 @@ public final class ResponderConfig implements Parcelable {
};
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}