Merge "Only set task surface background in freeform" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 43d2ae9..f47766e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -1335,13 +1335,13 @@
private void handleOpTimeoutLocked() {
switch (mVerb) {
case VERB_BINDING:
- onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
+ // The system may have been too busy. Don't drop the job or trigger an ANR.
+ onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ true,
/* texCounterMetricId */
"job_scheduler.value_cntr_w_uid_slow_app_response_binding",
/* debugReason */ "timed out while binding",
/* anrMessage */ "Timed out while trying to bind",
- CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
- mRunningJob.getUid()));
+ /* triggerAnr */ false);
break;
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
diff --git a/api/Android.bp b/api/Android.bp
index 45e70719..222275f 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -82,7 +82,6 @@
"framework-media",
"framework-mediaprovider",
"framework-ondevicepersonalization",
- "framework-pdf",
"framework-permission",
"framework-permission-s",
"framework-scheduling",
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 8713126..796a0c6 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -226,40 +226,6 @@
android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101]
android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101]
-android/telecom/Call.java:94: lint: unable to parse link/see tag: #playDtmfTone(char [101]
-android/telecom/CallControl.java:163: lint: Unresolved link/see tag "android.telecom.CallStreamingService CallStreamingService" in android.telecom.CallControl [101]
-android/telecom/CallControlCallback.java:63: lint: Unresolved link/see tag "android.telecom.CallAttributes.CallType" in android.telecom.CallControlCallback [101]
-android/telecom/PhoneAccountSuggestion.java:17: lint: Unresolved link/see tag "android.telecom.PhoneAccountSuggestionService PhoneAccountSuggestionService" in android.telecom.PhoneAccountSuggestion [101]
-android/telephony/CarrierConfigManager.java:2934: lint: Unresolved link/see tag "android.telephony.TelephonyManager.PremiumCapability TelephonyManager.PremiumCapability" in android.telephony.CarrierConfigManager [101]
-android/telephony/CarrierConfigManager.java:4020: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:4108: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:3920: lint: Unresolved link/see tag "android.telephony.ims.SipDelegateManager" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:4001: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/CarrierConfigManager.java:4089: lint: Unresolved link/see tag "ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.CarrierConfigManager.Ims [101]
-android/telephony/NetworkRegistrationInfo.java:131: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.NetworkRegistrationInfo [101]
-android/telephony/PhoneStateListener.java:293: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.PhoneStateListener [101]
-android/telephony/PhoneStateListener.java:544: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.PhoneStateListener [101]
-android/telephony/SubscriptionManager.java:1385: lint: Unresolved link/see tag "Build.VERSION_CODES.P" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101]
-android/telephony/SubscriptionManager.java:1385: lint: Unresolved link/see tag "Build.VERSION_CODES.V" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101]
-android/telephony/SubscriptionPlan.java:134: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.SubscriptionPlan [101]
-android/telephony/SubscriptionPlan.java:288: lint: Unresolved link/see tag "android.telephony.Annotation.NetworkType NetworkType" in android.telephony.SubscriptionPlan.Builder [101]
-android/telephony/TelephonyCallback.java:113: lint: Unresolved link/see tag "android.telephony.PreciseDisconnectCause PreciseDisconnectCause" in android.telephony.TelephonyCallback.CallDisconnectCauseListener [101]
-android/telephony/TelephonyManager.java:2544: lint: Unresolved link/see tag "android.telephony.TelephonyManager.CarrierRestrictionStatus CarrierRestrictionStatus" in android.telephony.TelephonyManager [101]
-android/telephony/TelephonyManager.java:2841: lint: Unresolved link/see tag "android.telephony.TelephonyManager.SetOpportunisticSubscriptionResult TelephonyManager.SetOpportunisticSubscriptionResult" in android.telephony.TelephonyManager [101]
-android/telephony/TelephonyManager.java:3273: lint: Unresolved link/see tag "android.telephony.TelephonyManager.PurchasePremiumCapabilityResult PurchasePremiumCapabilityResult" in android.telephony.TelephonyManager [101]
-android/telephony/TelephonyManager.java:3989: lint: Unresolved link/see tag "android.telephony.TelephonyManager.MobileDataPolicy MobileDataPolicy" in android.telephony.TelephonyManager [101]
-android/telephony/data/ApnSetting.java:39: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV4() DataCallResponse#getMtuV4()" in android.telephony.data.ApnSetting [101]
-android/telephony/data/ApnSetting.java:50: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV6() DataCallResponse#getMtuV6()" in android.telephony.data.ApnSetting [101]
-android/telephony/data/ApnSetting.java:597: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV4() DataCallResponse#getMtuV4()" in android.telephony.data.ApnSetting.Builder [101]
-android/telephony/data/ApnSetting.java:611: lint: Unresolved link/see tag "android.telephony.data.DataCallResponse#getMtuV6() DataCallResponse#getMtuV6()" in android.telephony.data.ApnSetting.Builder [101]
-android/telephony/euicc/EuiccManager.java:331: lint: Unresolved link/see tag "android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE" in android.telephony.euicc.EuiccManager [101]
-android/telephony/ims/ImsMmTelManager.java:117: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.ImsMmTelManager [101]
-android/telephony/ims/ImsRcsManager.java:43: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.ImsRcsManager [101]
-android/telephony/ims/ProvisioningManager.java:102: lint: Unresolved link/see tag "android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability MmTelFeature.MmTelCapabilities.MmTelCapability" in android.telephony.ims.ProvisioningManager [101]
-android/telephony/ims/ProvisioningManager.java:102: lint: Unresolved link/see tag "android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech ImsRegistrationImplBase.ImsRegistrationTech" in android.telephony.ims.ProvisioningManager [101]
-android/telephony/ims/ProvisioningManager.java:136: lint: Unresolved link/see tag "android.telephony.ims.ImsRcsManager.RcsImsCapabilityFlag ImsRcsManager.RcsImsCapabilityFlag" in android.telephony.ims.ProvisioningManager [101]
-android/telephony/ims/RegistrationManager.java:21: lint: Unresolved link/see tag "android.telephony.ims.feature.ImsFeature ImsFeature" in android.telephony.ims.RegistrationManager [101]
-android/telephony/ims/RegistrationManager.java:24: lint: Unresolved link/see tag "android.telephony.ims.ImsService ImsService" in android.telephony.ims.RegistrationManager [101]
android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101]
android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101]
android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101]
@@ -291,76 +257,6 @@
android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101]
android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_30" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_35" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_40" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_45" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_50" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_55" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_60" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_65" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_70" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_75" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_80" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_85" in android [101]
-com/android/server/wm/CompatModePackages.java:92: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_90" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_30" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_35" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_40" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_45" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_50" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_55" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_60" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_65" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_70" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_75" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_80" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_85" in android [101]
-com/android/server/wm/CompatModePackages.java:122: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_90" in android [101]
-com/android/server/wm/CompatModePackages.java:135: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:135: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:135: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_90" in android [101]
-com/android/server/wm/CompatModePackages.java:148: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:148: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:148: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_85" in android [101]
-com/android/server/wm/CompatModePackages.java:161: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:161: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:161: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_80" in android [101]
-com/android/server/wm/CompatModePackages.java:174: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:174: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:174: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_75" in android [101]
-com/android/server/wm/CompatModePackages.java:187: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:187: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:187: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_70" in android [101]
-com/android/server/wm/CompatModePackages.java:200: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:200: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:200: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_65" in android [101]
-com/android/server/wm/CompatModePackages.java:213: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:213: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:213: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_60" in android [101]
-com/android/server/wm/CompatModePackages.java:226: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:226: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:226: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_55" in android [101]
-com/android/server/wm/CompatModePackages.java:239: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:239: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:239: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_50" in android [101]
-com/android/server/wm/CompatModePackages.java:252: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:252: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:252: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_45" in android [101]
-com/android/server/wm/CompatModePackages.java:265: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:265: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:265: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_40" in android [101]
-com/android/server/wm/CompatModePackages.java:278: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:278: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:278: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_35" in android [101]
-com/android/server/wm/CompatModePackages.java:291: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED" in android [101]
-com/android/server/wm/CompatModePackages.java:291: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALED_INVERSE" in android [101]
-com/android/server/wm/CompatModePackages.java:291: lint: Unresolved link/see tag "CompatModePackages#DOWNSCALE_30" in android [101]
-
android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]
android/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101]
android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
@@ -370,7 +266,6 @@
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]
android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101]
-android/telephony/TelephonyRegistryManager.java:242: lint: Unresolved link/see tag "#listenFromListener" in android [101]
android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101]
android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101]
com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]
@@ -393,7 +288,6 @@
com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]
com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]
com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101]
-android/telephony/SubscriptionManager.java:1370: lint: Unresolved link/see tag "Build.VERSION_CODES.Q" in android.telephony.SubscriptionManager.OnSubscriptionsChangedListener [101]
android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131]
android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131]
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index db751a4..aa48451 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1903,7 +1903,7 @@
method public android.media.PlaybackParams setAudioStretchMode(int);
}
- public final class RingtoneSelection {
+ @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public final class RingtoneSelection {
method @NonNull public static android.media.RingtoneSelection fromUri(@Nullable android.net.Uri, int);
method public int getSoundSource();
method @Nullable public android.net.Uri getSoundUri();
@@ -1915,14 +1915,16 @@
field public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; // 0x3
field public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; // 0x1
field public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; // 0x2
- field public static final int SOUND_SOURCE_DEFAULT = 0; // 0x0
field public static final int SOUND_SOURCE_OFF = 1; // 0x1
+ field public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
+ field public static final int SOUND_SOURCE_UNSPECIFIED = 0; // 0x0
field public static final int SOUND_SOURCE_URI = 2; // 0x2
- field public static final int VIBRATION_SOURCE_APPLICATION_PROVIDED = 3; // 0x3
+ field public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4; // 0x4
field public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; // 0xa
- field public static final int VIBRATION_SOURCE_DEFAULT = 0; // 0x0
field public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; // 0xb
field public static final int VIBRATION_SOURCE_OFF = 1; // 0x1
+ field public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
+ field public static final int VIBRATION_SOURCE_UNSPECIFIED = 0; // 0x0
field public static final int VIBRATION_SOURCE_URI = 2; // 0x2
}
@@ -2610,17 +2612,17 @@
package android.os.vibrator.persistence {
- public class ParsedVibration {
+ @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public class ParsedVibration {
method @NonNull public java.util.List<android.os.VibrationEffect> getVibrationEffects();
method @Nullable public android.os.VibrationEffect resolve(@NonNull android.os.Vibrator);
}
- public final class VibrationXmlParser {
+ @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public final class VibrationXmlParser {
method @Nullable public static android.os.vibrator.persistence.ParsedVibration parseDocument(@NonNull java.io.Reader) throws java.io.IOException;
method @Nullable public static android.os.VibrationEffect parseVibrationEffect(@NonNull java.io.Reader) throws java.io.IOException;
}
- public final class VibrationXmlSerializer {
+ @FlaggedApi("android.os.vibrator.enable_vibration_serialization_apis") public final class VibrationXmlSerializer {
method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException;
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 107be8b..93e39d5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -191,8 +191,14 @@
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_DEFAULT
UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_OFF:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_OFF
+UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_SYSTEM_DEFAULT:
+ New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_SYSTEM_DEFAULT
+UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_UNSPECIFIED:
+ New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_UNSPECIFIED
UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_URI:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_URI
+UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_DEFAULT:
+ New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_DEFAULT
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_PROVIDED:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_PROVIDED
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_AUDIO_CHANNEL:
@@ -203,6 +209,10 @@
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_HAPTIC_GENERATOR
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_OFF:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_OFF
+UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_SYSTEM_DEFAULT:
+ New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_SYSTEM_DEFAULT
+UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_UNSPECIFIED:
+ New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_UNSPECIFIED
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_URI:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_URI
UnflaggedApi: android.media.RingtoneSelection#fromUri(android.net.Uri, int):
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3bf2cca..f68681b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4288,7 +4288,7 @@
}
/**
- * Start monitoring changes to the imoportance of uids running in the system.
+ * Start monitoring changes to the importance of uids running in the system.
* @param listener The listener callback that will receive change reports.
* @param importanceCutpoint The level of importance in which the caller is interested
* in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9a90df9..e12181a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -101,6 +101,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.ResourcesImpl;
import android.content.res.loader.ResourcesLoader;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
@@ -297,6 +298,7 @@
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
public static final boolean DEBUG_ORDER = false;
+ private static final boolean DEBUG_APP_INFO = true;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
/**
* The delay to release the provider when it has no more references. It reduces the number of
@@ -6473,10 +6475,35 @@
resApk.updateApplicationInfo(ai, oldPaths);
}
+ ResourcesImpl beforeImpl = getApplication().getResources().getImpl();
+
synchronized (mResourcesManager) {
// Update all affected Resources objects to use new ResourcesImpl
mResourcesManager.applyAllPendingAppInfoUpdates();
}
+
+ ResourcesImpl afterImpl = getApplication().getResources().getImpl();
+
+ if ((beforeImpl != afterImpl) && !Arrays.equals(beforeImpl.getAssets().getApkAssets(),
+ afterImpl.getAssets().getApkAssets())) {
+ List<String> beforeAssets = Arrays.asList(beforeImpl.getAssets().getApkPaths());
+ List<String> afterAssets = Arrays.asList(afterImpl.getAssets().getApkPaths());
+
+ List<String> onlyBefore = new ArrayList<>(beforeAssets);
+ onlyBefore.removeAll(afterAssets);
+ List<String> onlyAfter = new ArrayList<>(afterAssets);
+ onlyAfter.removeAll(beforeAssets);
+
+ Slog.i(TAG, "ApplicationInfo updating for " + ai.packageName + ", new timestamp: "
+ + ai.createTimestamp + "\nassets removed: " + onlyBefore + "\nassets added: "
+ + onlyAfter);
+
+ if (DEBUG_APP_INFO) {
+ Slog.v(TAG, "ApplicationInfo updating for " + ai.packageName
+ + ", assets before change: " + beforeAssets + "\n assets after change: "
+ + afterAssets);
+ }
+ }
}
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ca10d14..1aa8ebe 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1486,13 +1486,13 @@
AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
- * Allows the assistant app to get the training data from the PCC sandbox to improve the
+ * Allows the assistant app to get the training data from the trusted process to improve the
* hotword training model.
*
* @hide
*/
- public static final int OP_RECEIVE_SANDBOX_TRAINING_DATA =
- AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRAINING_DATA;
+ public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
+ AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1640,7 +1640,7 @@
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_SANDBOX_TRAINING_DATA
+ OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA
})
public @interface AppOpString {}
@@ -2261,13 +2261,13 @@
"android:receive_sandbox_trigger_audio";
/**
- * Allows the assistant app to get the training data from the PCC sandbox to improve
+ * Allows the assistant app to get the training data from the trusted process to improve
* the hotword training model.
*
* @hide
*/
- public static final String OPSTR_RECEIVE_SANDBOX_TRAINING_DATA =
- "android:receive_sandbox_training_data";
+ public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA =
+ "android:receive_trusted_process_training_data";
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -2811,9 +2811,9 @@
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
"RECEIVE_SANDBOX_TRIGGER_AUDIO")
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
- new AppOpInfo.Builder(OP_RECEIVE_SANDBOX_TRAINING_DATA,
- OPSTR_RECEIVE_SANDBOX_TRAINING_DATA,
- "RECEIVE_SANDBOX_TRAINING_DATA").build()
+ new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
+ OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA,
+ "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/companion/virtualnative/OWNERS b/core/java/android/companion/virtualnative/OWNERS
new file mode 100644
index 0000000..2968104
--- /dev/null
+++ b/core/java/android/companion/virtualnative/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ea0f049..3e12a1a 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -13,3 +13,10 @@
description: "Bind wallpaper service on its own thread instead of system_server's main handler during a user switch."
bug: "302100344"
}
+
+flag {
+ name: "support_communal_profile"
+ namespace: "multiuser"
+ description: "Framework support for communal profile."
+ bug: "285426179"
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 231e4bc..1b130a9 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -53,6 +53,15 @@
mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
}
+ /**
+ * Returns candidate provider data list.
+ *
+ * @hide
+ */
+ public List<GetCredentialProviderData> getCandidateProviderDataList() {
+ return mCandidateProviderDataList;
+ }
+
protected GetCandidateCredentialsResponse(Parcel in) {
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
in.readTypedList(candidateProviderDataList, GetCredentialProviderData.CREATOR);
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index bbfed24..883f157 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -82,8 +82,8 @@
DVORAK(4, LAYOUT_TYPE_DVORAK),
COLEMAK(5, LAYOUT_TYPE_COLEMAK),
WORKMAN(6, LAYOUT_TYPE_WORKMAN),
- TURKISH_F(7, LAYOUT_TYPE_TURKISH_F),
- TURKISH_Q(8, LAYOUT_TYPE_TURKISH_Q),
+ TURKISH_Q(7, LAYOUT_TYPE_TURKISH_Q),
+ TURKISH_F(8, LAYOUT_TYPE_TURKISH_F),
EXTENDED(9, LAYOUT_TYPE_EXTENDED);
private final int mValue;
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index d8e60c8..88f62f3 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -19,4 +19,11 @@
name: "haptics_customization_ringtone_v2_enabled"
description: "Enables the usage of the new RingtoneV2 class"
bug: "241918098"
-}
\ No newline at end of file
+}
+
+flag {
+ namespace: "haptics"
+ name: "enable_vibration_serialization_apis"
+ description: "Enables the APIs for vibration serialization/deserialization."
+ bug: "245129509"
+}
diff --git a/core/java/android/os/vibrator/persistence/ParsedVibration.java b/core/java/android/os/vibrator/persistence/ParsedVibration.java
index ded74ea..3d1deea 100644
--- a/core/java/android/os/vibrator/persistence/ParsedVibration.java
+++ b/core/java/android/os/vibrator/persistence/ParsedVibration.java
@@ -16,6 +16,7 @@
package android.os.vibrator.persistence;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -34,6 +35,7 @@
*
* @hide
*/
+@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
@TestApi
public class ParsedVibration {
private final List<VibrationEffect> mEffects;
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index e08cc42..fed1053 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -16,6 +16,7 @@
package android.os.vibrator.persistence;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -115,6 +116,7 @@
*
* @hide
*/
+@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
@TestApi
public final class VibrationXmlParser {
private static final String TAG = "VibrationXmlParser";
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index 1cdfa4f..2880454 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -16,6 +16,7 @@
package android.os.vibrator.persistence;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
@@ -42,6 +43,7 @@
*
* @hide
*/
+@FlaggedApi(android.os.vibrator.Flags.FLAG_ENABLE_VIBRATION_SERIALIZATION_APIS)
@TestApi
public final class VibrationXmlSerializer {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b414ed4..36d3180 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11607,6 +11607,45 @@
"accessibility_magnification_joystick_enabled";
/**
+ * Controls magnification enable gesture. Accessibility magnification can have one or more
+ * enable gestures.
+ *
+ * @see #ACCESSIBILITY_MAGNIFICATION_GESTURE_NONE
+ * @see #ACCESSIBILITY_MAGNIFICATION_GESTURE_SINGLE_FINGER_TRIPLE_TAP
+ * @see #ACCESSIBILITY_MAGNIFICATION_GESTURE_TWO_FINGER_TRIPLE_TAP
+ * @hide
+ */
+ public static final String ACCESSIBILITY_MAGNIFICATION_GESTURE =
+ "accessibility_magnification_gesture";
+
+ /**
+ * Magnification enable gesture value that is a default value.
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_GESTURE_NONE = 0x0;
+
+ /**
+ * Magnification enable gesture value is single finger triple tap.
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_GESTURE_SINGLE_FINGER_TRIPLE_TAP = 0x1;
+
+ /**
+ * Magnification enable gesture value is two finger triple tap.
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_GESTURE_TWO_FINGER_TRIPLE_TAP = 0x2;
+
+ /**
+ * Magnification enable gesture values include single finger triple tap and two finger
+ * triple tap.
+ * @hide
+ */
+ public static final int ACCESSIBILITY_MAGNIFICATION_GESTURE_ALL =
+ ACCESSIBILITY_MAGNIFICATION_GESTURE_SINGLE_FINGER_TRIPLE_TAP
+ | ACCESSIBILITY_MAGNIFICATION_GESTURE_TWO_FINGER_TRIPLE_TAP;
+
+ /**
* Controls magnification capability. Accessibility magnification is capable of at least one
* of the magnification modes.
*
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 115894f..a29bf7a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -18,6 +18,7 @@
import static android.view.autofill.Helper.sDebug;
+import android.annotation.Hide;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,6 +27,7 @@
import android.annotation.TestApi;
import android.content.ClipData;
import android.content.IntentSender;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -187,6 +189,9 @@
@Nullable private final InlinePresentation mInlinePresentation;
@Nullable private final InlinePresentation mInlineTooltipPresentation;
private final IntentSender mAuthentication;
+
+ @Nullable private final Bundle mAuthenticationExtras;
+
@Nullable String mId;
/**
@@ -224,6 +229,7 @@
mInlinePresentation = inlinePresentation;
mInlineTooltipPresentation = inlineTooltipPresentation;
mAuthentication = authentication;
+ mAuthenticationExtras = null;
mId = id;
}
@@ -246,6 +252,7 @@
mInlinePresentation = dataset.mInlinePresentation;
mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
mAuthentication = dataset.mAuthentication;
+ mAuthenticationExtras = dataset.mAuthenticationExtras;
mId = dataset.mId;
mAutofillDatatypes = dataset.mAutofillDatatypes;
}
@@ -264,6 +271,7 @@
mInlinePresentation = builder.mInlinePresentation;
mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mAuthentication = builder.mAuthentication;
+ mAuthenticationExtras = builder.mAuthenticationExtras;
mId = builder.mId;
mAutofillDatatypes = builder.mAutofillDatatypes;
}
@@ -345,6 +353,12 @@
}
/** @hide */
+ @Hide
+ public @Nullable Bundle getAuthenticationExtras() {
+ return mAuthenticationExtras;
+ }
+
+ /** @hide */
@TestApi
public boolean isEmpty() {
return mFieldIds == null || mFieldIds.isEmpty();
@@ -401,6 +415,9 @@
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
+ if (mAuthenticationExtras != null) {
+ builder.append(", hasAuthenticationExtras");
+ }
if (mAutofillDatatypes != null) {
builder.append(", autofillDatatypes=").append(mAutofillDatatypes);
}
@@ -454,6 +471,8 @@
@Nullable private InlinePresentation mInlinePresentation;
@Nullable private InlinePresentation mInlineTooltipPresentation;
private IntentSender mAuthentication;
+
+ private Bundle mAuthenticationExtras;
private boolean mDestroyed;
@Nullable private String mId;
@@ -624,6 +643,25 @@
}
/**
+ * Sets extras to be associated with the {@code authentication} intent sender, to be
+ * set on the intent that is fired through the intent sender.
+ *
+ * Autofill providers can set any extras they wish to receive directly on the intent
+ * that is used to create the {@code authentication}. This is an internal API, to be
+ * used by the platform to associate data with a given dataset. These extras will be
+ * merged with the {@code clientState} and sent as part of the fill in intent when
+ * the {@code authentication} intentSender is invoked.
+ *
+ * @hide
+ */
+ @Hide
+ public @NonNull Builder setAuthenticationExtras(@Nullable Bundle authenticationExtra) {
+ throwIfDestroyed();
+ mAuthenticationExtras = authenticationExtra;
+ return this;
+ }
+
+ /**
* Sets the id for the dataset so its usage can be tracked.
*
* <p>Dataset usage can be tracked for 2 purposes:
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index be7b722..a2b0a66 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -153,6 +153,18 @@
public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST =
"android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
+ /**
+ * The key to autofillId associated with the requested credential option and the corresponding
+ * credential entry. The associated autofillId will be contained inside the candidate query
+ * bundle of {@link android.credentials.CredentialOption} if requested through the
+ * {@link com.android.credentialmanager.autofill.CredentialAutofillService}. The resulting
+ * credential entry will contain the autofillId inside its framework extras intent.
+ *
+ * @hide
+ */
+ public static final String EXTRA_AUTOFILL_ID =
+ "android.service.credentials.extra.AUTOFILL_ID";
+
private static final String TAG = "CredProviderService";
/**
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index f6309f2..cf1156d 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -401,7 +401,7 @@
/**
* Listen for call disconnect causes which contains {@link DisconnectCause} and
- * {@link PreciseDisconnectCause}.
+ * the precise disconnect cause.
*
* <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
* or the calling app has carrier privileges
@@ -851,8 +851,8 @@
* subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param disconnectCause {@link DisconnectCause}.
- * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
+ * @param disconnectCause the disconnect cause
+ * @param preciseDisconnectCause the precise disconnect cause
* @deprecated Use {@link TelephonyCallback.CallDisconnectCauseListener} instead.
*/
@RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index fb2d771..7b48a16 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -221,7 +221,7 @@
}
/**
- * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to.
+ * Return an array containing all network types this SubscriptionPlan applies to.
* @see TelephonyManager for network types values
*/
public @NonNull @NetworkType int[] getNetworkTypes() {
@@ -365,7 +365,7 @@
* Set the network types this SubscriptionPlan applies to. By default the plan will apply
* to all network types. An empty array means this plan applies to no network types.
*
- * @param networkTypes an array of all {@link NetworkType}s that apply to this plan.
+ * @param networkTypes an array of all network types that apply to this plan.
* @see TelephonyManager for network type values
*/
public @NonNull Builder setNetworkTypes(@NonNull @NetworkType int[] networkTypes) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 7ada058..19bcf28 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -948,8 +948,8 @@
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param disconnectCause {@link DisconnectCause}.
- * @param preciseDisconnectCause {@link PreciseDisconnectCause}.
+ * @param disconnectCause the disconnect cause
+ * @param preciseDisconnectCause the precise disconnect cause
*/
@RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
void onCallDisconnectCauseChanged(@Annotation.DisconnectCauses int disconnectCause,
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index e2c5539..0063d13 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -238,7 +238,7 @@
}
/**
- * To check the SDK version for {@link #listenFromListener}.
+ * To check the SDK version for {@code #listenFromListener}.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
diff --git a/core/java/android/view/IDecorViewGestureListener.aidl b/core/java/android/view/IDecorViewGestureListener.aidl
new file mode 100644
index 0000000..1022dbf
--- /dev/null
+++ b/core/java/android/view/IDecorViewGestureListener.aidl
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Listener for changes to gesture interception detector running at DecorView.
+ *
+ * {@hide}
+ */
+oneway interface IDecorViewGestureListener {
+ /**
+ * Called when a DecorView has started intercepting gesture.
+ *
+ * @param windowToken Where did this gesture interception result comes from.
+ * @param intercepted Whether the gesture interception detector has started interception.
+ */
+ void onInterceptionChanged(in IBinder windowToken, in boolean intercepted);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index cccac95..c10fc9f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -48,6 +48,7 @@
import android.view.RemoteAnimationAdapter;
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
+import android.view.IDecorViewGestureListener;
import android.view.IWallpaperVisibilityListener;
import android.view.IWindow;
import android.view.IWindowSession;
@@ -1062,4 +1063,18 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.ACCESS_SURFACE_FLINGER)")
boolean replaceContentOnDisplay(int displayId, in SurfaceControl sc);
+
+ /**
+ * Registers a DecorView gesture listener for a given display.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MONITOR_INPUT)")
+ void registerDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
+
+ /**
+ * Unregisters a DecorView gesture listener for a given display.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MONITOR_INPUT)")
+ void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 83de2a0..7acf2f8 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -284,6 +284,11 @@
oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects);
/**
+ * Called when the DecorView gesture interception state has changed.
+ */
+ oneway void reportDecorViewGestureInterceptionChanged(IWindow window, in boolean intercepted);
+
+ /**
* Called when the keep-clear areas for this window have changed.
*/
oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> restricted,
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cf46bcc..aa47f07 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5320,6 +5320,29 @@
}
/**
+ * Called from DecorView when gesture interception state has changed.
+ *
+ * @param intercepted If DecorView is intercepting touch events
+ */
+ public void updateDecorViewGestureInterception(boolean intercepted) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(
+ MSG_DECOR_VIEW_GESTURE_INTERCEPTION,
+ /* arg1= */ intercepted ? 1 : 0,
+ /* arg2= */ 0));
+ }
+
+ void decorViewInterceptionChanged(boolean intercepted) {
+ if (mView != null) {
+ try {
+ mWindowSession.reportDecorViewGestureInterceptionChanged(mWindow, intercepted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Set the root-level system gesture exclusion rects. These are added to those provided by
* the root's view hierarchy.
*/
@@ -5942,6 +5965,7 @@
private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 35;
private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
+ private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
final class ViewRootHandler extends Handler {
@Override
@@ -6220,6 +6244,9 @@
case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
systemGestureExclusionChanged();
} break;
+ case MSG_DECOR_VIEW_GESTURE_INTERCEPTION: {
+ decorViewInterceptionChanged(/* intercepted= */ msg.arg1 == 1);
+ } break;
case MSG_KEEP_CLEAR_RECTS_CHANGED: {
keepClearRectsChanged(/* accessibilityFocusRectChanged= */ msg.arg1 == 1);
} break;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 8fe9b7b..7c3b6ae 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -584,9 +584,13 @@
}
@Override
- public void reportKeepClearAreasChanged(android.view.IWindow window, List<Rect> restrictedRects,
- List<Rect> unrestrictedRects) {
- }
+ public void reportDecorViewGestureInterceptionChanged(IWindow window, boolean intercepted) {}
+
+ @Override
+ public void reportKeepClearAreasChanged(
+ android.view.IWindow window,
+ List<Rect> restrictedRects,
+ List<Rect> unrestrictedRects) {}
@Override
public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 89fa83e..a95f2ef 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -277,6 +277,12 @@
"android.view.autofill.extra.CLIENT_STATE";
/**
+ * @hide
+ */
+ public static final String EXTRA_AUTH_STATE =
+ "android.view.autofill.extra.AUTH_STATE";
+
+ /**
* Intent extra: the {@link android.view.inputmethod.InlineSuggestionsRequest} in the
* autofill request.
*
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 1be916f..8566263 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -290,11 +290,12 @@
};
private Consumer<Boolean> mCrossWindowBlurEnabledListener;
+ private final WearGestureInterceptionDetector mWearGestureInterceptionDetector;
+
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
mFeatureId = featureId;
-
mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
@@ -314,6 +315,11 @@
updateLogTag(params);
mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
+
+ mWearGestureInterceptionDetector =
+ WearGestureInterceptionDetector.isEnabled(context)
+ ? new WearGestureInterceptionDetector(context, this)
+ : null;
}
void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -544,6 +550,18 @@
}
}
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null && mWearGestureInterceptionDetector != null) {
+ boolean wasIntercepting = mWearGestureInterceptionDetector.isIntercepting();
+ boolean intercepting = mWearGestureInterceptionDetector.onInterceptTouchEvent(event);
+ if (wasIntercepting != intercepting) {
+ viewRootImpl.updateDecorViewGestureInterception(intercepting);
+ }
+ if (intercepting) {
+ return true;
+ }
+ }
+
if (!SWEEP_OPEN_MENU) {
return false;
}
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
new file mode 100644
index 0000000..6fd5018
--- /dev/null
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+
+/**
+ * Wear-specific gesture interception detector to be installed at DecorView, for compatibility of
+ * apps depending on legacy SwipeDismissLayout behavior.
+ *
+ * <p>Results of the detector will be used by {@code DecorView} to intercept motion events. The
+ * interception state will also be sent to {@code android.view.ViewRootImpl} and {@code
+ * com.android.server.wm.DisplayContent} through {@code android.view.IWindowSession}.
+ *
+ * <p>SystemUI can register {@code android.view.IDecorViewGestureListener} to listen for the result
+ * of the detector. The result will be valid for between a pair of touch down/up events.
+ */
+public class WearGestureInterceptionDetector {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WearGestureInterceptionDetector";
+
+ private final DecorView mInstalledDecorView;
+ private final float mTouchSlop;
+ private final float mSwipingStartThreshold;
+ private boolean mSwiping;
+
+ private float mDownX;
+ private float mDownY;
+ private int mActivePointerId;
+ private boolean mDiscardIntercept;
+
+ WearGestureInterceptionDetector(Context context, DecorView installedDecorView) {
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mInstalledDecorView = installedDecorView;
+ mSwipingStartThreshold = mTouchSlop * 2;
+ }
+
+ /** Check if this gesture interception detector should be enabled. */
+ public static boolean isEnabled(Context context) {
+ PackageManager pm = context.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ return false;
+ }
+
+ // Compatibility check for flag that disables legacy SwipeDismissLayout.
+ TypedArray windowAttr =
+ context.obtainStyledAttributes(new int[] {android.R.attr.windowSwipeToDismiss});
+ boolean windowSwipeToDismiss = true;
+ if (windowAttr.getIndexCount() > 0) {
+ windowSwipeToDismiss = windowAttr.getBoolean(0, true);
+ }
+ windowAttr.recycle();
+ return windowSwipeToDismiss;
+ }
+
+ private boolean isPointerIndexValid(MotionEvent ev) {
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ if (DEBUG) {
+ Log.e(TAG, "Invalid pointer index: ignoring.");
+ }
+ mDiscardIntercept = true;
+ return false;
+ }
+ return true;
+ }
+
+ private void updateSwiping(MotionEvent ev) {
+ if (mSwiping) {
+ return;
+ }
+ float deltaX = ev.getRawX() - mDownX;
+ float deltaY = ev.getRawY() - mDownY;
+ // Check if we have left the touch slop area.
+ if ((deltaX * deltaX) + (deltaY * deltaY) > (mTouchSlop * mTouchSlop)) {
+ mSwiping = deltaX > mSwipingStartThreshold && Math.abs(deltaY) < Math.abs(deltaX);
+ }
+ }
+
+ private void updateDiscardIntercept(MotionEvent ev) {
+ if (!mSwiping) {
+ // Don't look at canScroll until we have passed the touch slop
+ return;
+ }
+ if (mDiscardIntercept) {
+ return;
+ }
+ final boolean checkLeft = mDownX < ev.getRawX();
+ final float x = ev.getX(mActivePointerId);
+ final float y = ev.getY(mActivePointerId);
+ if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
+ mDiscardIntercept = true;
+ }
+ }
+
+ /** Resets internal members when canceling. */
+ private void resetMembers() {
+ mDownX = 0;
+ mDownY = 0;
+ mSwiping = false;
+ mDiscardIntercept = false;
+ }
+
+ /** Should we intercept the MotionEvent for system gesture? */
+ public boolean isIntercepting() {
+ return !mDiscardIntercept && mSwiping;
+ }
+
+ /** Tests if the MotionEvent should be intercepted */
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ resetMembers();
+ mDownX = ev.getRawX();
+ mDownY = ev.getRawY();
+ mActivePointerId = ev.getPointerId(0);
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mActivePointerId = ev.getPointerId(ev.getActionIndex());
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ int associatedPointerIndex = ev.getActionIndex();
+ if (ev.getPointerId(associatedPointerIndex) == mActivePointerId) {
+ // This was our active pointer going up.
+ // Choose the first available pointer index.
+ int newActionIndex = associatedPointerIndex == 0 ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newActionIndex);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mDiscardIntercept) {
+ break;
+ }
+ if (!isPointerIndexValid(ev)) {
+ break;
+ }
+ updateSwiping(ev);
+ updateDiscardIntercept(ev);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ resetMembers();
+ break;
+ }
+ return isIntercepting();
+ }
+
+ /**
+ * Tests scroll-ability within child views of v in the direction of dx.
+ *
+ * @param v View to test for horizontal scroll-ability
+ * @param checkSelf Whether the view v passed should itself be checked for scroll-ability
+ * (true), or just its children (false).
+ * @param checkLeft Which direction to check? Left = true, right = false.
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ private boolean canScroll(View v, boolean checkSelf, boolean checkLeft, float x, float y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+ final int scrollX = v.getScrollX();
+ final int scrollY = v.getScrollY();
+ final int count = group.getChildCount();
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = group.getChildAt(i);
+
+ if (x + scrollX < child.getLeft()
+ || x + scrollX >= child.getRight()
+ || y + scrollY < child.getTop()
+ || y + scrollY >= child.getBottom()) {
+ // This child is out of bound, don't bother checking.
+ continue;
+ }
+
+ // Recursively check until finding the first scrollable or none is scrollable.
+ if (canScroll(
+ /* view= */ child,
+ /* checkSelf= */ true,
+ /* checkLeft= */ checkLeft,
+ /* x= */ x + scrollX - child.getLeft(),
+ /* y= */ y + scrollY - child.getTop())) {
+ return true;
+ }
+ }
+ }
+
+ return checkSelf && v.canScrollHorizontally(checkLeft ? -1 : 1);
+ }
+}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 6c93680..4732702 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -98,6 +98,7 @@
// Settings for font scaling
optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto accessibility_magnification_gesture = 53 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/values-watch/dimens_material.xml b/core/res/res/values-watch/dimens_material.xml
index 2ab2d91..8becb08 100644
--- a/core/res/res/values-watch/dimens_material.xml
+++ b/core/res/res/values-watch/dimens_material.xml
@@ -47,11 +47,12 @@
<dimen name="progress_bar_height">24dp</dimen>
<!-- Progress bar message dimens -->
- <dimen name="message_progress_dialog_text_size">18sp</dimen>
+ <dimen name="message_progress_dialog_text_size">14sp</dimen>
<dimen name="message_progress_dialog_bottom_padding">80px</dimen>
<dimen name="message_progress_dialog_top_padding">0dp</dimen>
<dimen name="message_progress_dialog_start_padding">0dp</dimen>
<dimen name="message_progress_dialog_end_padding">0dp</dimen>
+ <item name="message_progress_dialog_letter_spacing" format="float" type="dimen">0.021</item>
<!-- fallback for screen percentage widths -->
<dimen name="screen_percentage_05">0dp</dimen>
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index 8698e86..f3e412d 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -100,7 +100,9 @@
<style name="ProgressDialogMessage">
<item name="android:textAlignment">center</item>
- <item name="textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:fontFamily">google-sans-text</item>
+ <item name="android:letterSpacing">@dimen/message_progress_dialog_letter_spacing</item>
+ <item name="textColor">?attr/textColorPrimary</item>
<item name="textSize">@dimen/message_progress_dialog_text_size</item>
<item name="paddingBottom">@dimen/message_progress_dialog_bottom_padding</item>
<item name="paddingEnd">@dimen/message_progress_dialog_end_padding</item>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4360c5a..7ef81ab 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1151,6 +1151,14 @@
<!-- Allows activities to be launched on a long press on power during device setup. -->
<bool name="config_allowStartActivityForLongPressOnPowerInSetup">false</bool>
+ <!-- Control the behavior when the user short presses the settings button.
+ 0 - Nothing
+ 1 - Launch notification panel
+ This needs to match the constants in
+ com/android/server/policy/PhoneWindowManager.java
+ -->
+ <integer name="config_shortPressOnSettingsBehavior">0</integer>
+
<!-- Control the behavior when the user short presses the power button.
0 - Nothing
1 - Go to sleep (doze)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7f30695..643f4b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1816,6 +1816,7 @@
<java-symbol type="integer" name="config_lidNavigationAccessibility" />
<java-symbol type="integer" name="config_lidOpenRotation" />
<java-symbol type="integer" name="config_longPressOnHomeBehavior" />
+ <java-symbol type="integer" name="config_shortPressOnSettingsBehavior" />
<java-symbol type="layout" name="global_actions" />
<java-symbol type="layout" name="global_actions_item" />
<java-symbol type="layout" name="global_actions_silent_mode" />
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index 3b9d7ba..3ec44d1 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -553,6 +553,7 @@
openAidlClients(/* numClients= */ 1);
ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
mTunerSessions[0].tune(initialSel);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any());
doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
mTunerSessions[0].cancel();
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e7814cb..d1aceaf 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -114,6 +114,7 @@
throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
}
throwIfCannotDraw(bitmap);
+ bitmap.setGainmap(null);
mNativeCanvasWrapper = nInitRaster(bitmap.getNativeInstance());
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
@@ -178,7 +179,7 @@
throw new IllegalStateException();
}
throwIfCannotDraw(bitmap);
-
+ bitmap.setGainmap(null);
nSetBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance());
mDensity = bitmap.mDensity;
}
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 0f3488b..31c2eb2 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -28,8 +28,8 @@
import android.util.Log;
/**
- * @hide This is the client side for IKeystoreUserManager AIDL.
- * It shall only be used by the LockSettingsService.
+ * @hide This is the client side for IKeystoreMaintenance AIDL.
+ * It is used mainly by LockSettingsService.
*/
public class AndroidKeyStoreMaintenance {
private static final String TAG = "AndroidKeyStoreMaintenance";
@@ -66,7 +66,7 @@
}
/**
- * Informs Keystore 2.0 about removing a usergit mer
+ * Informs Keystore 2.0 about removing a user
*
* @param userId - Android user id of the user being removed
* @return 0 if successful or a {@code ResponseCode}
@@ -91,7 +91,7 @@
*
* @param userId - Android user id of the user
* @param password - a secret derived from the synthetic password provided by the
- * LockSettingService
+ * LockSettingsService
* @return 0 if successful or a {@code ResponseCode}
* @hide
*/
@@ -110,7 +110,7 @@
}
/**
- * Informs Keystore 2.0 that an app was uninstalled and the corresponding namspace is to
+ * Informs Keystore 2.0 that an app was uninstalled and the corresponding namespace is to
* be cleared.
*/
public static int clearNamespace(@Domain int domain, long namespace) {
@@ -172,10 +172,10 @@
* namespace.
*
* @return * 0 on success
- * * KEY_NOT_FOUND if the source did not exists.
+ * * KEY_NOT_FOUND if the source did not exist.
* * PERMISSION_DENIED if any of the required permissions was missing.
* * INVALID_ARGUMENT if the destination was occupied or any domain value other than
- * the allowed once were specified.
+ * the allowed ones was specified.
* * SYSTEM_ERROR if an unexpected error occurred.
*/
public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) {
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7a309f5..1f6f7ae 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -143,7 +143,9 @@
<dimen name="bubble_expanded_view_padding">16dp</dimen>
<!-- Padding for the edge of the expanded view that is closest to the edge of the screen used
when displaying in landscape on a large screen. -->
- <dimen name="bubble_expanded_view_largescreen_landscape_padding">128dp</dimen>
+ <dimen name="bubble_expanded_view_largescreen_landscape_padding">102dp</dimen>
+ <!-- The width of the expanded view on large screens. -->
+ <dimen name="bubble_expanded_view_largescreen_width">540dp</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>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index ea7053d..17e06e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -54,10 +54,6 @@
public static final float FLYOUT_MAX_WIDTH_PERCENT_LARGE_SCREEN = 0.3f;
/** The max percent of screen width to use for the flyout on phone. */
public static final float FLYOUT_MAX_WIDTH_PERCENT = 0.6f;
- /** The percent of screen width for the expanded view on a large screen. **/
- private static final float EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT = 0.48f;
- /** The percent of screen width for the expanded view on a large screen. **/
- private static final float EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT = 0.70f;
/** The percent of screen width for the expanded view on a small tablet. **/
private static final float EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT = 0.72f;
/** The percent of screen width for the expanded view when shown in the bubble bar. **/
@@ -95,6 +91,7 @@
private int mPointerWidth;
private int mPointerHeight;
private int mPointerOverlap;
+ private int mManageButtonHeightIncludingMargins;
private int mManageButtonHeight;
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
@@ -176,21 +173,20 @@
mExpandedViewLargeScreenWidth = (int) (bounds.width()
* EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT);
} else {
- mExpandedViewLargeScreenWidth = isLandscape()
- ? (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_LANDSCAPE_WIDTH_PERCENT)
- : (int) (bounds.width() * EXPANDED_VIEW_LARGE_SCREEN_PORTRAIT_WIDTH_PERCENT);
+ mExpandedViewLargeScreenWidth =
+ res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width);
}
if (mIsLargeScreen) {
- if (isLandscape() && !mIsSmallTablet) {
+ if (mIsSmallTablet) {
+ final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
+ mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
+ mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
+ } else {
mExpandedViewLargeScreenInsetClosestEdge = res.getDimensionPixelSize(
R.dimen.bubble_expanded_view_largescreen_landscape_padding);
mExpandedViewLargeScreenInsetFurthestEdge = bounds.width()
- mExpandedViewLargeScreenInsetClosestEdge
- mExpandedViewLargeScreenWidth;
- } else {
- final int centeredInset = (bounds.width() - mExpandedViewLargeScreenWidth) / 2;
- mExpandedViewLargeScreenInsetClosestEdge = centeredInset;
- mExpandedViewLargeScreenInsetFurthestEdge = centeredInset;
}
} else {
mExpandedViewLargeScreenInsetClosestEdge = mExpandedViewPadding;
@@ -202,7 +198,9 @@
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mPointerOverlap = res.getDimensionPixelSize(R.dimen.bubble_pointer_overlap);
- mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+ mManageButtonHeightIncludingMargins =
+ res.getDimensionPixelSize(R.dimen.bubble_manage_button_total_height);
+ mManageButtonHeight = res.getDimensionPixelSize(R.dimen.bubble_manage_button_height);
mExpandedViewMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mMinimumFlyoutWidthLargeScreen = res.getDimensionPixelSize(
@@ -420,7 +418,7 @@
int pointerSize = showBubblesVertically()
? mPointerWidth
: (mPointerHeight + mPointerMargin);
- int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ int bottomPadding = isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
return getAvailableRect().height()
- expandedContainerY
- paddingTop
@@ -438,6 +436,15 @@
// overflow in landscape on phone is max
return MAX_HEIGHT;
}
+
+ if (mIsLargeScreen && !mIsSmallTablet && !isOverflow) {
+ // the expanded view height on large tablets is calculated based on the shortest screen
+ // size and is the same in both portrait and landscape
+ int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
+ int shortestScreenSide = Math.min(mScreenRect.height(), mScreenRect.width());
+ return shortestScreenSide - 2 * maxVerticalInset - mManageButtonHeight;
+ }
+
float desiredHeight = isOverflow
? mOverflowHeight
: ((Bubble) bubble).getDesiredHeight(mContext);
@@ -466,7 +473,8 @@
return topAlignment;
}
// If we're here, we're showing vertically & developer has made height less than maximum.
- int manageButtonHeight = isOverflow ? mExpandedViewPadding : mManageButtonHeight;
+ int manageButtonHeight =
+ isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
float pointerPosition = getPointerPosition(bubblePosition);
float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
float topIfCentered = pointerPosition - (expandedViewHeight / 2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index 58d9a64..287a97c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -22,20 +22,24 @@
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import android.util.DisplayMetrics;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -257,6 +261,27 @@
assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
}
+ @Test
+ public void testExpandedViewHeight_onLargeTablet() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ new WindowManagerConfig()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .setUpConfig();
+ mPositioner.update();
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ int manageButtonHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+ float expectedHeight = 1800 - 2 * 20 - manageButtonHeight;
+ assertThat(mPositioner.getExpandedViewHeight(bubble)).isWithin(0.1f).of(expectedHeight);
+ }
+
/**
* Calculates the Y position bubbles should be placed based on the config. Based on
* the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
@@ -323,6 +348,8 @@
? MIN_WIDTH_FOR_TABLET
: MIN_WIDTH_FOR_TABLET - 1;
mConfiguration.orientation = mOrientation;
+ mConfiguration.screenWidthDp = pxToDp(mScreenBounds.width());
+ mConfiguration.screenHeightDp = pxToDp(mScreenBounds.height());
when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
WindowInsets windowInsets = mock(WindowInsets.class);
@@ -331,5 +358,11 @@
when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
}
+
+ private int pxToDp(float px) {
+ int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
+ float dp = px / ((float) dpi / DisplayMetrics.DENSITY_DEFAULT);
+ return (int) dp;
+ }
}
}
diff --git a/media/java/android/media/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java
index 74f7276..b74b6a3 100644
--- a/media/java/android/media/RingtoneSelection.java
+++ b/media/java/android/media/RingtoneSelection.java
@@ -18,16 +18,23 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.net.Uri;
+import android.os.UserHandle;
+import android.os.vibrator.Flags;
import android.provider.MediaStore;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* Immutable representation a desired ringtone, usually originating from a user preference.
@@ -46,7 +53,7 @@
* to be internally consistent and reflect effective values - with the exception of not verifying
* the actual URI content. For example, loading a selection Uri that sets a sound source to
* {@link #SOUND_SOURCE_URI}, but doesn't also have a sound Uri set, will result in this class
- * instead returning {@link #SOUND_SOURCE_DEFAULT} from {@link #getSoundSource}.
+ * instead returning {@link #SOUND_SOURCE_UNSPECIFIED} from {@link #getSoundSource}.
*
* <h2>Storing preferences</h2>
*
@@ -57,6 +64,7 @@
* @hide
*/
@TestApi
+@FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
public final class RingtoneSelection {
/**
@@ -70,7 +78,7 @@
* The sound source is not explicitly specified, so it can follow default behavior for its
* context.
*/
- public static final int SOUND_SOURCE_DEFAULT = 0;
+ public static final int SOUND_SOURCE_UNSPECIFIED = 0;
/**
* Sound is explicitly disabled, such as the user having selected "Silent" in the sound picker.
@@ -83,15 +91,25 @@
public static final int SOUND_SOURCE_URI = 2;
/**
+ * The sound should explicitly use the system default.
+ *
+ * <p>This value isn't valid within the system default itself.
+ */
+ public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3;
+
+ // Note: Value 4 reserved for possibility of SOURCE_SOURCE_APPLICATION_DEFAULT.
+
+ /**
* Directive for how to make sound.
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "SOUND_SOURCE_", value = {
SOUND_SOURCE_UNKNOWN,
- SOUND_SOURCE_DEFAULT,
+ SOUND_SOURCE_UNSPECIFIED,
SOUND_SOURCE_OFF,
SOUND_SOURCE_URI,
+ SOUND_SOURCE_SYSTEM_DEFAULT,
})
public @interface SoundSource {}
@@ -106,9 +124,9 @@
/**
* Vibration source is not explicitly specified. If vibration is enabled, this will use the
* first available of {@link #VIBRATION_SOURCE_AUDIO_CHANNEL},
- * {@link #VIBRATION_SOURCE_APPLICATION_PROVIDED}, or system default vibration.
+ * {@link #VIBRATION_SOURCE_APPLICATION_DEFAULT}, or {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
*/
- public static final int VIBRATION_SOURCE_DEFAULT = 0;
+ public static final int VIBRATION_SOURCE_UNSPECIFIED = 0;
/** Specifies that vibration is explicitly disabled for this ringtone. */
public static final int VIBRATION_SOURCE_OFF = 1;
@@ -117,22 +135,31 @@
public static final int VIBRATION_SOURCE_URI = 2;
/**
+ * The vibration should explicitly use the system default.
+ *
+ * <p>This value isn't valid within the system default itself.
+ */
+ public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3;
+
+ /**
* Specifies that vibration should use the vibration provided by the application. This is
* typically the application's own default for the use-case, provided via
* {@link Ringtone.Builder#setVibrationEffect}. For notification channels, this is the vibration
* effect saved on the notification channel.
*
* <p>If no vibration is specified by the application, this value behaves if the source was
- * {@link #VIBRATION_SOURCE_DEFAULT}.
+ * {@link #VIBRATION_SOURCE_UNSPECIFIED}.
+ *
+ * <p>This value isn't valid within the system default.
*/
- public static final int VIBRATION_SOURCE_APPLICATION_PROVIDED = 3;
+ public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4;
/**
* Specifies that vibration should use haptic audio channels from the
* sound Uri. If the sound URI doesn't have haptic channels, then reverts to the order specified
- * by {@link #VIBRATION_SOURCE_DEFAULT}.
+ * by {@link #VIBRATION_SOURCE_UNSPECIFIED}.
*/
- // Numeric gap from VIBRATION_SOURCE_APPLICATION_PROVIDED in case we want other common elements.
+ // Numeric gap from VIBRATION_SOURCE_APPLICATION_DEFAULT in case we want other common elements.
public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10;
/**
@@ -151,10 +178,10 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "VIBRATION_SOURCE_", value = {
VIBRATION_SOURCE_UNKNOWN,
- VIBRATION_SOURCE_DEFAULT,
+ VIBRATION_SOURCE_UNSPECIFIED,
VIBRATION_SOURCE_OFF,
VIBRATION_SOURCE_URI,
- VIBRATION_SOURCE_APPLICATION_PROVIDED,
+ VIBRATION_SOURCE_APPLICATION_DEFAULT,
VIBRATION_SOURCE_AUDIO_CHANNEL,
VIBRATION_SOURCE_HAPTIC_GENERATOR,
})
@@ -162,9 +189,12 @@
/**
* Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the sound Uri
- * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF}.
- * This behavior is particularly suited to loading values from older settings that may contain
- * a raw sound Uri or null for silent.
+ * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF},
+ * and symbolic default URIs ({@link RingtoneManager#getDefaultUri}) meaning
+ * {@link #SOUND_SOURCE_SYSTEM_DEFAULT}.
+ *
+ * <p>This behavior is particularly suited to loading values from older settings that may
+ * contain a raw sound Uri or null for silent.
*
* <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
*/
@@ -173,7 +203,8 @@
/**
* Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the vibration
* Uri for the returned {@link RingtoneSelection}, with null meaning
- * {@link #VIBRATION_SOURCE_OFF}.
+ * {@link #VIBRATION_SOURCE_OFF} and symbolic default URIs
+ * ({@link RingtoneManager#getDefaultUri}) meaning {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
*
* <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
*/
@@ -182,7 +213,9 @@
/**
* Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as an invalid
* value. Null or an invalid values will revert to default behavior correspnoding to
- * {@link #DEFAULT_SELECTION_URI_STRING}.
+ * {@link #DEFAULT_SELECTION_URI_STRING}. Symbolic default URIs
+ * ({@link RingtoneManager#getDefaultUri}) will set both
+ * {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
*
* <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false,
* which include {@code null}.
@@ -218,10 +251,11 @@
/* Common param values */
private static final String SOURCE_OFF_STRING = "off";
+ private static final String SOURCE_SYSTEM_DEFAULT_STRING = "sys";
/* Vibration source param values. */
private static final String VIBRATION_SOURCE_AUDIO_CHANNEL_STRING = "ac";
- private static final String VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING = "app";
+ private static final String VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING = "app";
private static final String VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING = "hg";
@Nullable
@@ -236,53 +270,45 @@
private RingtoneSelection(@Nullable Uri soundUri, @SoundSource int soundSource,
@Nullable Uri vibrationUri, @VibrationSource int vibrationSource) {
- // Enforce guarantees on the source values: revert to unset if they depend on something
- // that's not set.
- switch (soundSource) {
- case SOUND_SOURCE_URI:
- case SOUND_SOURCE_UNKNOWN: // Allow unknown to revert to URI before default.
- mSoundSource = soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_DEFAULT;
- break;
- default:
- mSoundSource = soundSource;
- break;
- }
- switch (vibrationSource) {
- case VIBRATION_SOURCE_AUDIO_CHANNEL:
- case VIBRATION_SOURCE_HAPTIC_GENERATOR:
- mVibrationSource = soundUri != null ? vibrationSource : VIBRATION_SOURCE_DEFAULT;
- break;
- case VIBRATION_SOURCE_URI:
- case VIBRATION_SOURCE_UNKNOWN: // Allow unknown to revert to URI.
- mVibrationSource =
- vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_DEFAULT;
- break;
- default:
- mVibrationSource = vibrationSource;
- break;
- }
+ // Enforce guarantees on the source values: revert to unspecified if they depend on
+ // something that's not set.
+ //
+ // The non-public "unknown" value can't appear in a getter result, it's just a reserved
+ // "null" value and should be treated the same as an unrecognized value. This can be seen
+ // in Uri parsing. For this and other unrecognized values, we either revert them to the URI
+ // source, if a Uri was included, or the "unspecified" source otherwise. This can be
+ // seen in action in the Uri parsing.
+ //
+ // The "unspecified" source is a public value meaning that there is no specific
+ // behavior indicated, and the defaults and fallbacks should be applied. For example, an
+ // vibration source value of "system default" means to explicitly use the system default
+ // vibration. However, an "unspecified" vibration source will first see if audio coupled
+ // or application-default vibrations are available.
+ mSoundSource = switch (soundSource) {
+ // Supported explicit values that don't have a Uri.
+ case SOUND_SOURCE_OFF, SOUND_SOURCE_UNSPECIFIED, SOUND_SOURCE_SYSTEM_DEFAULT ->
+ soundSource;
+ // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
+ // unspecified.
+ default ->
+ soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_UNSPECIFIED;
+ };
+ mVibrationSource = switch (vibrationSource) {
+ // Enforce vibration sources that require a sound Uri.
+ case VIBRATION_SOURCE_AUDIO_CHANNEL, VIBRATION_SOURCE_HAPTIC_GENERATOR ->
+ soundUri != null ? vibrationSource : VIBRATION_SOURCE_UNSPECIFIED;
+ // Supported explicit values that don't rely on any Uri.
+ case VIBRATION_SOURCE_OFF, VIBRATION_SOURCE_UNSPECIFIED,
+ VIBRATION_SOURCE_SYSTEM_DEFAULT, VIBRATION_SOURCE_APPLICATION_DEFAULT ->
+ vibrationSource;
+ // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
+ // unspecified.
+ default ->
+ vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_UNSPECIFIED;
+ };
// Clear Uri values if they're un-used by the source.
- switch (mSoundSource) {
- case SOUND_SOURCE_OFF:
- mSoundUri = null;
- break;
- default:
- // Unset case isn't handled here: the defaulting behavior is left to the player.
- mSoundUri = soundUri;
- break;
- }
- switch (mVibrationSource) {
- case VIBRATION_SOURCE_OFF:
- case VIBRATION_SOURCE_APPLICATION_PROVIDED:
- case VIBRATION_SOURCE_AUDIO_CHANNEL:
- case VIBRATION_SOURCE_HAPTIC_GENERATOR:
- mVibrationUri = null;
- break;
- default:
- // Unset case isn't handled here: the defaulting behavior is left to the player.
- mVibrationUri = vibrationUri;
- break;
- }
+ mSoundUri = mSoundSource == SOUND_SOURCE_URI ? soundUri : null;
+ mVibrationUri = mVibrationSource == VIBRATION_SOURCE_URI ? vibrationUri : null;
}
/**
@@ -360,18 +386,83 @@
}
// Any URI content://media/ringtone
return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
- && MediaStore.AUTHORITY.equals(uri.getAuthority())
+ && MediaStore.AUTHORITY.equals(
+ ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()))
&& MEDIA_URI_RINGTONE_PATH.equals(uri.getPath());
}
+ /**
+ * Strip the specified userId from internal Uris. Non-stripped userIds will typically be
+ * for work profiles referencing system ringtones from the host user.
+ *
+ * This is only for use in RingtoneManager.
+ * @hide
+ */
+ @VisibleForTesting
+ public RingtoneSelection getWithoutUserId(int userIdToStrip) {
+ if (mSoundSource != SOUND_SOURCE_URI && mVibrationSource != VIBRATION_SOURCE_URI) {
+ return this;
+ }
+ // Ok if uri is null. We only replace explicit references to the specified (current) userId.
+ int soundUserId = ContentProvider.getUserIdFromUri(mSoundUri, UserHandle.USER_NULL);
+ int vibrationUserId = ContentProvider.getUserIdFromUri(mVibrationUri, UserHandle.USER_NULL);
+ boolean needToChangeSound =
+ soundUserId != UserHandle.USER_NULL && soundUserId == userIdToStrip;
+ boolean needToChangeVibration =
+ vibrationUserId != UserHandle.USER_NULL && vibrationUserId == userIdToStrip;
+
+ // Anything to do?
+ if (!needToChangeSound && !needToChangeVibration) {
+ return this;
+ }
+
+ RingtoneSelection.Builder updated = new Builder(this);
+ // The relevant uris can't be null, because they contain userIdToStrip.
+ if (needToChangeSound) {
+ // mSoundUri is not null, so the result of getUriWithoutUserId won't be null.
+ updated.setSoundSource(ContentProvider.getUriWithoutUserId(mSoundUri));
+ }
+ if (needToChangeVibration) {
+ updated.setVibrationSource(ContentProvider.getUriWithoutUserId(mVibrationUri));
+ }
+ return updated.build();
+ }
+
+ @Override
+ public String toString() {
+ return toUri().toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof RingtoneSelection other)) {
+ return false;
+ }
+ return this.mSoundSource == other.mSoundSource
+ && this.mVibrationSource == other.mVibrationSource
+ && Objects.equals(this.mSoundUri, other.mSoundUri)
+ && Objects.equals(this.mVibrationUri, other.mVibrationUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSoundSource, mVibrationSource, mSoundUri, mVibrationUri);
+ }
/**
* Converts a Uri into a RingtoneSelection.
*
- * <p>Null values and Uris that {@link #isRingtoneSelectionUri(Uri)} returns false will be
- * treated according to the behaviour specified by the {@code unrecognizedValueBehavior}
- * parameter.
+ * <p>Null values, Uris that {@link #isRingtoneSelectionUri(Uri)} returns false (except for
+ * old-style symbolic default Uris {@link RingtoneManager#getDefaultUri}) will be treated
+ * according to the behaviour specified by the {@code unrecognizedValueBehavior} parameter.
+ *
+ * <p>Symbolic default Uris (where {@link RingtoneManager#getDefaultType(Uri)} returns -1,
+ * will map sources to {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and
+ * {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
*
* @param uri The Uri to convert, potentially null.
* @param unrecognizedValueBehavior indicates how to treat values for which
@@ -384,21 +475,35 @@
if (isRingtoneSelectionUri(uri)) {
return parseRingtoneSelectionUri(uri);
}
+ // Old symbolic default URIs map to the sources suggested by the unrecognized behavior.
+ // It doesn't always map to both sources because the app may have its own default behavior
+ // to apply, so non-primary sources are left as unspecified, which will revert to the
+ // system default in the absence of an app default.
+ boolean isDefaultUri = RingtoneManager.getDefaultType(uri) > 0;
RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
switch (unrecognizedValueBehavior) {
case FROM_URI_RINGTONE_SELECTION_ONLY:
- // Always return use-defaults for unrecognized ringtone selection Uris.
+ if (isDefaultUri) {
+ builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
+ builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT);
+ }
+ // Always return unspecified (defaults) for unrecognized ringtone selection Uris.
return builder.build();
case FROM_URI_RINGTONE_SELECTION_OR_SOUND:
if (uri == null) {
return builder.setSoundSource(SOUND_SOURCE_OFF).build();
+ } else if (isDefaultUri) {
+ return builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT).build();
} else {
return builder.setSoundSource(uri).build();
}
case FROM_URI_RINGTONE_SELECTION_OR_VIBRATION:
if (uri == null) {
return builder.setVibrationSource(VIBRATION_SOURCE_OFF).build();
+ } else if (isDefaultUri) {
+ return builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT).build();
} else {
+ // Unlike sound, there's no legacy settings alias uri for the default.
return builder.setVibrationSource(uri).build();
}
default:
@@ -407,7 +512,12 @@
}
}
- /** Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}. */
+ /**
+ * Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}.
+ * As a special case to be more compatible, if the RingtoneSelection has a userId specified in
+ * the authority, then this is pushed down into the sound and vibration Uri, as the selection
+ * itself is agnostic.
+ */
@NonNull
private static RingtoneSelection parseRingtoneSelectionUri(@NonNull Uri uri) {
RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
@@ -416,19 +526,39 @@
uri.getQueryParameter(URI_PARAM_VIBRATION_SOURCE));
Uri soundUri = getParamAsUri(uri, URI_PARAM_SOUND_URI);
Uri vibrationUri = getParamAsUri(uri, URI_PARAM_VIBRATION_URI);
+
+ // Add userId if necessary. This won't override an existing one in the sound/vib Uris.
+ int userIdFromAuthority = ContentProvider.getUserIdFromAuthority(
+ uri.getAuthority(), UserHandle.USER_NULL);
+ if (userIdFromAuthority != UserHandle.USER_NULL) {
+ // Won't override existing user id.
+ if (soundUri != null) {
+ soundUri = ContentProvider.maybeAddUserId(soundUri, userIdFromAuthority);
+ }
+ if (vibrationUri != null) {
+ vibrationUri = ContentProvider.maybeAddUserId(vibrationUri, userIdFromAuthority);
+ }
+ }
+
+ // Set sound uri if present, but map system default Uris to the system default source.
if (soundUri != null) {
- builder.setSoundSource(soundUri);
+ if (RingtoneManager.getDefaultType(uri) >= 0) {
+ builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
+ } else {
+ builder.setSoundSource(soundUri);
+ }
}
if (vibrationUri != null) {
builder.setVibrationSource(vibrationUri);
}
+
// Don't set the source if there's a URI and the source is default, because that will
// override the Uri source set above. In effect, we prioritise "explicit" sources over
// an implicit Uri source - except for "default", which isn't really explicit.
- if (soundSource != SOUND_SOURCE_DEFAULT || soundUri == null) {
+ if (soundSource != SOUND_SOURCE_UNSPECIFIED || soundUri == null) {
builder.setSoundSource(soundSource);
}
- if (vibrationSource != VIBRATION_SOURCE_DEFAULT || vibrationUri == null) {
+ if (vibrationSource != VIBRATION_SOURCE_UNSPECIFIED || vibrationUri == null) {
builder.setVibrationSource(vibrationSource);
}
return builder.build();
@@ -450,26 +580,28 @@
*/
@Nullable
private static String soundSourceToString(@SoundSource int soundSource) {
- switch (soundSource) {
- case SOUND_SOURCE_OFF: return SOURCE_OFF_STRING;
- default: return null;
- }
+ return switch (soundSource) {
+ case SOUND_SOURCE_OFF -> SOURCE_OFF_STRING;
+ case SOUND_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
+ default -> null;
+ };
}
/**
* Returns the sound source int corresponding to the query string value. Returns
* {@link #SOUND_SOURCE_UNKNOWN} if the value isn't recognised, and
- * {@link #SOUND_SOURCE_DEFAULT} if the value is {@code null} (not in the Uri).
+ * {@link #SOUND_SOURCE_UNSPECIFIED} if the value is {@code null} (not in the Uri).
*/
@SoundSource
private static int stringToSoundSource(@Nullable String soundSource) {
if (soundSource == null) {
- return SOUND_SOURCE_DEFAULT;
+ return SOUND_SOURCE_UNSPECIFIED;
}
- switch (soundSource) {
- case SOURCE_OFF_STRING: return SOUND_SOURCE_OFF;
- default: return SOUND_SOURCE_UNKNOWN;
- }
+ return switch (soundSource) {
+ case SOURCE_OFF_STRING -> SOUND_SOURCE_OFF;
+ case SOURCE_SYSTEM_DEFAULT_STRING -> SOUND_SOURCE_SYSTEM_DEFAULT;
+ default -> SOUND_SOURCE_UNKNOWN;
+ };
}
/**
@@ -478,30 +610,31 @@
*/
@Nullable
private static String vibrationSourceToString(@VibrationSource int vibrationSource) {
- switch (vibrationSource) {
- case VIBRATION_SOURCE_OFF: return SOURCE_OFF_STRING;
- case VIBRATION_SOURCE_AUDIO_CHANNEL: return VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
- case VIBRATION_SOURCE_HAPTIC_GENERATOR:
- return VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
- case VIBRATION_SOURCE_APPLICATION_PROVIDED:
- return VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING;
- default: return null;
- }
+ return switch (vibrationSource) {
+ case VIBRATION_SOURCE_OFF -> SOURCE_OFF_STRING;
+ case VIBRATION_SOURCE_AUDIO_CHANNEL -> VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
+ case VIBRATION_SOURCE_HAPTIC_GENERATOR -> VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
+ case VIBRATION_SOURCE_APPLICATION_DEFAULT ->
+ VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING;
+ case VIBRATION_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
+ default -> null;
+ };
}
@VibrationSource
private static int stringToVibrationSource(@Nullable String vibrationSource) {
if (vibrationSource == null) {
- return VIBRATION_SOURCE_DEFAULT;
+ return VIBRATION_SOURCE_UNSPECIFIED;
}
- switch (vibrationSource) {
- case SOURCE_OFF_STRING: return VIBRATION_SOURCE_OFF;
- case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING: return VIBRATION_SOURCE_AUDIO_CHANNEL;
- case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING: return VIBRATION_SOURCE_HAPTIC_GENERATOR;
- case VIBRATION_SOURCE_APPLICATION_PROVIDED_STRING:
- return VIBRATION_SOURCE_APPLICATION_PROVIDED;
- default: return VIBRATION_SOURCE_UNKNOWN;
- }
+ return switch (vibrationSource) {
+ case SOURCE_OFF_STRING -> VIBRATION_SOURCE_OFF;
+ case SOURCE_SYSTEM_DEFAULT_STRING -> VIBRATION_SOURCE_SYSTEM_DEFAULT;
+ case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING -> VIBRATION_SOURCE_AUDIO_CHANNEL;
+ case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING -> VIBRATION_SOURCE_HAPTIC_GENERATOR;
+ case VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING ->
+ VIBRATION_SOURCE_APPLICATION_DEFAULT;
+ default -> VIBRATION_SOURCE_UNKNOWN;
+ };
}
/**
@@ -512,12 +645,13 @@
public static final class Builder {
private Uri mSoundUri;
private Uri mVibrationUri;
- @SoundSource private int mSoundSource = SOUND_SOURCE_DEFAULT;
- @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_DEFAULT;
+ @SoundSource private int mSoundSource = SOUND_SOURCE_UNSPECIFIED;
+ @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_UNSPECIFIED;
/**
* Creates a new {@link RingtoneSelection} builder. A default ringtone selection has its
- * sound and vibration source unset, which means they would fall back to system defaults.
+ * sound and vibration source unspecified, which means they would fall back to app/system
+ * defaults.
*/
public Builder() {}
@@ -559,7 +693,9 @@
*/
@NonNull
public Builder setSoundSource(@NonNull Uri soundUri) {
- mSoundUri = requireNonNull(soundUri);
+ // getCanonicalUri shouldn't return null. If it somehow did, then the
+ // RingtoneSelection constructor will revert this to unspecified.
+ mSoundUri = requireNonNull(soundUri).getCanonicalUri();
mSoundSource = SOUND_SOURCE_URI;
return this;
}
@@ -587,7 +723,9 @@
*/
@NonNull
public Builder setVibrationSource(@NonNull Uri vibrationUri) {
- mVibrationUri = requireNonNull(vibrationUri);
+ // getCanonicalUri shouldn't return null. If it somehow did, then the
+ // RingtoneSelection constructor will revert this to unspecified.
+ mVibrationUri = requireNonNull(vibrationUri).getCanonicalUri();
mVibrationSource = VIBRATION_SOURCE_URI;
return this;
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index ba88484..2318bb9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -18,21 +18,23 @@
import android.app.assist.AssistStructure
import android.content.Context
-import android.credentials.GetCredentialRequest
import android.credentials.CredentialManager
-import android.credentials.GetCandidateCredentialsResponse
import android.credentials.CredentialOption
import android.credentials.GetCandidateCredentialsException
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCredentialRequest
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
-import android.service.autofill.FillRequest
import android.service.autofill.AutofillService
-import android.service.autofill.FillResponse
import android.service.autofill.FillCallback
-import android.service.autofill.SaveRequest
+import android.service.autofill.FillRequest
+import android.service.autofill.FillResponse
import android.service.autofill.SaveCallback
+import android.service.autofill.SaveRequest
+import android.service.credentials.CredentialProviderService
import android.util.Log
+import android.view.autofill.AutofillId
import org.json.JSONObject
import java.util.concurrent.Executors
@@ -129,27 +131,31 @@
}
private fun traverseNode(
- viewNode: AssistStructure.ViewNode?,
+ viewNode: AssistStructure.ViewNode,
cmRequests: MutableList<CredentialOption>
) {
- val options = getCredentialOptionsFromViewNode(viewNode)
- cmRequests.addAll(options)
+ viewNode.autofillId?.let {
+ val options = getCredentialOptionsFromViewNode(viewNode, it)
+ cmRequests.addAll(options)
+ }
- val children: List<AssistStructure.ViewNode>? =
- viewNode?.run {
+ val children: List<AssistStructure.ViewNode> =
+ viewNode.run {
(0 until childCount).map { getChildAt(it) }
}
- children?.forEach { childNode: AssistStructure.ViewNode ->
+ children.forEach { childNode: AssistStructure.ViewNode ->
traverseNode(childNode, cmRequests)
}
}
- private fun getCredentialOptionsFromViewNode(viewNode: AssistStructure.ViewNode?):
- List<CredentialOption> {
+ private fun getCredentialOptionsFromViewNode(
+ viewNode: AssistStructure.ViewNode,
+ autofillId: AutofillId
+ ): List<CredentialOption> {
// TODO(b/293945193) Replace with isCredential check from viewNode
val credentialHints: MutableList<String> = mutableListOf()
- if (viewNode != null && viewNode.autofillHints != null) {
+ if (viewNode.autofillHints != null) {
for (hint in viewNode.autofillHints!!) {
if (hint.startsWith(CRED_HINT_PREFIX)) {
credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX))
@@ -159,12 +165,14 @@
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
for (credentialHint in credentialHints) {
- convertJsonToCredentialOption(credentialHint).let { credentialOptions.addAll(it) }
+ convertJsonToCredentialOption(credentialHint, autofillId)
+ .let { credentialOptions.addAll(it) }
}
return credentialOptions
}
- private fun convertJsonToCredentialOption(jsonString: String): List<CredentialOption> {
+ private fun convertJsonToCredentialOption(jsonString: String, autofillId: AutofillId):
+ List<CredentialOption> {
// TODO(b/302000646) Move this logic to jetpack so that is consistent
// with building the json
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
@@ -173,11 +181,14 @@
val options = json.getJSONArray(CRED_OPTIONS_KEY)
for (i in 0 until options.length()) {
val option = options.getJSONObject(i)
-
+ val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY))
+ candidateBundle.putParcelable(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ autofillId)
credentialOptions.add(CredentialOption(
option.getString(TYPE_KEY),
convertJsonToBundle(option.getJSONObject(REQUEST_DATA_KEY)),
- convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY)),
+ candidateBundle,
option.getBoolean(SYS_PROVIDER_REQ_KEY),
))
}
diff --git a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
index ad3199f..d9338b1 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_brazilian.kcm
@@ -337,7 +337,7 @@
label: 'M'
base: 'm'
shift, capslock: 'M'
- shift+capslock: 'n'
+ shift+capslock: 'm'
}
key COMMA {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
index 6744922..fc53cba 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_persian.kcm
@@ -12,9 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
-
-type FULL
+type OVERLAY
### Basic QWERTY keys ###
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 88bb30b..7f23f74 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -227,13 +227,6 @@
android:label="@string/keyboard_layout_turkish"
android:keyboardLayout="@raw/keyboard_layout_turkish"
android:keyboardLocale="tr-Latn"
- android:keyboardLayoutType="qwerty" />
-
- <keyboard-layout
- android:name="keyboard_layout_turkish"
- android:label="@string/keyboard_layout_turkish"
- android:keyboardLayout="@raw/keyboard_layout_turkish"
- android:keyboardLocale="tr-Latn"
android:keyboardLayoutType="turkish_q" />
<keyboard-layout
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
index d0a6188..0757df3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
@@ -29,8 +29,8 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
@@ -46,6 +46,7 @@
fun SettingsTextFieldPassword(
value: String,
label: String,
+ enabled: Boolean = true,
onTextChange: (String) -> Unit,
) {
var visibility by remember { mutableStateOf(false) }
@@ -60,6 +61,7 @@
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Send
),
+ enabled = enabled,
trailingIcon = {
Icon(
imageVector = if (visibility) Icons.Outlined.VisibilityOff
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 91d2d1b..ba4ad36 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -218,6 +218,7 @@
Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
Settings.Secure.NOTIFICATION_BUBBLES,
Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index bec1447..19fde75 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -308,6 +308,10 @@
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
+ new InclusiveIntegerRangeValidator(
+ Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE_NONE,
+ Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE_ALL));
VALIDATORS.put(
Secure.ACCESSIBILITY_BUTTON_TARGETS,
ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 3c8d4bc..f06b31c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1850,6 +1850,10 @@
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
+ SecureSettingsProto.Accessibility
+ .ACCESSIBILITY_MAGNIFICATION_GESTURE);
+ dumpSetting(s, p,
Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
SecureSettingsProto.Accessibility.HEARING_AID_RINGTONE_ROUTING);
dumpSetting(s, p,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 39173d9..58c7bdb 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -130,10 +130,23 @@
sealed interface UserAction
/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-object Back : UserAction
+data object Back : UserAction
/** The user swiped on the container. */
-enum class Swipe : UserAction {
+data class Swipe(
+ val direction: SwipeDirection,
+ val pointerCount: Int = 1,
+ val fromEdge: Edge? = null,
+) : UserAction {
+ companion object {
+ val Left = Swipe(SwipeDirection.Left)
+ val Up = Swipe(SwipeDirection.Up)
+ val Right = Swipe(SwipeDirection.Right)
+ val Down = Swipe(SwipeDirection.Down)
+ }
+}
+
+enum class SwipeDirection {
Up,
Down,
Left,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index c3a3752..ee310ab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -36,13 +36,14 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
@@ -99,7 +100,9 @@
return buildMap {
up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) }
left?.let { this[UserAction.Swipe(Direction.LEFT)] = SceneModel(left) }
- this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade)
+ this[UserAction.Swipe(fromEdge = Edge.TOP, direction = Direction.DOWN)] =
+ SceneModel(SceneKey.QuickSettings)
+ this[UserAction.Swipe(direction = Direction.DOWN)] = SceneModel(SceneKey.Shade)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 2ee461f..f35ea83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -22,6 +22,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
@@ -41,7 +42,12 @@
override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow<Map<UserAction, SceneModel>>(
mapOf(
- UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
+ UserAction.Swipe(
+ pointerCount = 2,
+ fromEdge = Edge.TOP,
+ direction = Direction.DOWN,
+ ) to SceneModel(SceneKey.QuickSettings),
+ UserAction.Swipe(direction = Direction.DOWN) to SceneModel(SceneKey.Shade),
)
)
.asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 6359ce60..0da562b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -35,15 +35,18 @@
import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Edge as SceneTransitionEdge
import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
import com.android.compose.animation.scene.observableTransitionState
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -181,12 +184,24 @@
private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
return when (this) {
is UserAction.Swipe ->
- when (this.direction) {
- Direction.LEFT -> Swipe.Left
- Direction.UP -> Swipe.Up
- Direction.RIGHT -> Swipe.Right
- Direction.DOWN -> Swipe.Down
- }
+ Swipe(
+ pointerCount = pointerCount,
+ fromEdge =
+ when (this.fromEdge) {
+ null -> null
+ Edge.LEFT -> SceneTransitionEdge.Left
+ Edge.TOP -> SceneTransitionEdge.Top
+ Edge.RIGHT -> SceneTransitionEdge.Right
+ Edge.BOTTOM -> SceneTransitionEdge.Bottom
+ },
+ direction =
+ when (this.direction) {
+ Direction.LEFT -> SwipeDirection.Left
+ Direction.UP -> SwipeDirection.Up
+ Direction.RIGHT -> SwipeDirection.Right
+ Direction.DOWN -> SwipeDirection.Down
+ }
+ )
is UserAction.Back -> Back
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index bb0cf6d4..eb7a735 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -34,7 +34,6 @@
import com.android.systemui.customization.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
@@ -80,8 +79,8 @@
private val batteryController: BatteryController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val configurationController: ConfigurationController,
- @DisplaySpecific private val resources: Resources,
- @DisplaySpecific private val context: Context,
+ @Main private val resources: Resources,
+ private val context: Context,
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
@KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
@@ -212,13 +211,13 @@
if (regionSamplingEnabled) {
clock?.let { clock ->
smallRegionSampler?.let {
- smallClockIsDark = it.currentRegionDarkness().isDark
- clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark)
+ val isRegionDark = it.currentRegionDarkness().isDark
+ clock.smallClock.events.onRegionDarknessChanged(isRegionDark)
}
largeRegionSampler?.let {
- largeClockIsDark = it.currentRegionDarkness().isDark
- clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark)
+ val isRegionDark = it.currentRegionDarkness().isDark
+ clock.largeClock.events.onRegionDarknessChanged(isRegionDark)
}
}
return
@@ -226,12 +225,12 @@
val isLightTheme = TypedValue()
context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
- smallClockIsDark = isLightTheme.data == 0
- largeClockIsDark = isLightTheme.data == 0
+ val isRegionDark = isLightTheme.data == 0
clock?.run {
- smallClock.events.onRegionDarknessChanged(smallClockIsDark)
- largeClock.events.onRegionDarknessChanged(largeClockIsDark)
+ Log.i(TAG, "Region isDark: $isRegionDark")
+ smallClock.events.onRegionDarknessChanged(isRegionDark)
+ largeClock.events.onRegionDarknessChanged(isRegionDark)
}
}
protected open fun createRegionSampler(
@@ -261,9 +260,6 @@
get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
private var cachedWeatherData: WeatherData? = null
- private var smallClockIsDark = true
- private var largeClockIsDark = true
-
private val configListener =
object : ConfigurationController.ConfigurationListener {
override fun onThemeChanged() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 51c0676..50be97e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -139,7 +139,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_password;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_unattended_update_password;
+ return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 714ba81..57151ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -321,7 +321,7 @@
resId = R.string.kg_prompt_after_user_lockdown_pattern;
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- resId = R.string.kg_prompt_unattended_update_pattern;
+ resId = R.string.kg_prompt_reason_timeout_pattern;
break;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 9d6d033..681aa70 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -123,7 +123,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_pin;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_unattended_update_pin;
+ return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index f9cc03ee..d848602 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -18,7 +18,6 @@
import static java.util.Collections.emptySet;
-import android.animation.LayoutTransition;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
@@ -79,14 +78,6 @@
mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
mMediaHostContainer = findViewById(R.id.status_view_media_container);
- if (mMediaHostContainer != null) {
- LayoutTransition mediaLayoutTransition = new LayoutTransition();
- ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition);
- mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
- mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
- mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
- mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING);
- }
updateDark();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 67b7052..79642bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -23,7 +23,6 @@
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.Animator;
-import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.res.Configuration;
@@ -50,14 +49,15 @@
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
+import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.power.shared.model.ScreenPowerState;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.power.shared.model.ScreenPowerState;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -175,27 +175,10 @@
return;
}
- final LayoutTransition mediaLayoutTransition =
- ((ViewGroup) mediaHostContainer).getLayoutTransition();
- if (mediaLayoutTransition == null) return;
-
- mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
+ ViewHierarchyAnimator.Companion.animateNextUpdate(mediaHostContainer,
+ Interpolators.STANDARD, /* duration= */ 500L,
+ /* animateChildren= */ false);
});
-
- mediaHostContainer.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- final LayoutTransition mediaLayoutTransition =
- ((ViewGroup) mediaHostContainer).getLayoutTransition();
- if (mediaLayoutTransition == null) return;
- if (!mediaLayoutTransition.isTransitionTypeEnabled(
- LayoutTransition.CHANGING)) {
- return;
- }
- // Note: when this is called, the LayoutTransition is already been set up.
- // Disables the LayoutTransition until it's explicitly enabled again.
- mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING);
- }
- );
}
mDumpManager.registerDumpable(getInstanceName(), this);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index a7b35ef..0d3f726 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -62,6 +62,7 @@
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -74,12 +75,15 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.Objects;
import java.util.function.Consumer;
@@ -128,6 +132,8 @@
@NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
@NonNull private final KeyguardInteractor mKeyguardInteractor;
@NonNull private final View.AccessibilityDelegate mAccessibilityDelegate;
+ @NonNull private final Lazy<BouncerInteractor> mBouncerInteractor;
+ @NonNull private final SceneContainerFlags mSceneContainerFlags;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -204,7 +210,9 @@
@NonNull KeyguardInteractor keyguardInteractor,
@NonNull FeatureFlags featureFlags,
PrimaryBouncerInteractor primaryBouncerInteractor,
- Context context
+ Context context,
+ Lazy<BouncerInteractor> bouncerInteractor,
+ SceneContainerFlags sceneContainerFlags
) {
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -233,6 +241,8 @@
dumpManager.registerDumpable(TAG, this);
mResources = resources;
mContext = context;
+ mBouncerInteractor = bouncerInteractor;
+ mSceneContainerFlags = sceneContainerFlags;
mAccessibilityDelegate = new View.AccessibilityDelegate() {
private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
@@ -715,7 +725,8 @@
return mDownDetected;
}
- private void onLongPress() {
+ @VisibleForTesting
+ protected void onLongPress() {
cancelTouches();
if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
@@ -732,7 +743,11 @@
// play device entry haptic (consistent with UDFPS controller longpress)
vibrateOnLongPress();
- mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
+ if (mSceneContainerFlags.isEnabled()) {
+ mBouncerInteractor.get().showOrUnlockDevice(null);
+ } else {
+ mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 0180384..7739021 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -87,7 +87,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -130,14 +129,14 @@
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import dagger.Lazy;
+
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
-import dagger.Lazy;
-
/**
* Class to handle ugly dependencies throughout sysui until we determine the
* long-term dependency injection solution.
@@ -298,7 +297,6 @@
@Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager;
- @Inject Lazy<NotificationGutsManager> mNotificationGutsManager;
@Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
@Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager;
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@@ -498,7 +496,6 @@
mProviders.put(NotificationLockscreenUserManager.class,
mNotificationLockscreenUserManager::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
- mProviders.put(NotificationGutsManager.class, mNotificationGutsManager::get);
mProviders.put(NotificationRemoteInputManager.class,
mNotificationRemoteInputManager::get);
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 4541384..b573fad 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -28,6 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.settings.UserTracker;
@@ -46,6 +47,7 @@
/**
* Manages notification when a guest session is resumed.
*/
+@SysUISingleton
public class GuestResumeSessionReceiver {
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 5b85ad0..1e29e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -2,7 +2,7 @@
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -51,7 +51,7 @@
* When this is `false`, an automatically-triggered face unlock shouldn't automatically dismiss
* the lockscreen.
*/
- fun isBypassEnabled(): Boolean
+ val isBypassEnabled: StateFlow<Boolean>
}
/** Encapsulates application state for device entry. */
@@ -68,7 +68,7 @@
) : DeviceEntryRepository {
override val isUnlocked =
- ConflatedCallbackFlow.conflatedCallbackFlow {
+ conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
override fun onUnlockedChanged() {
@@ -112,7 +112,24 @@
}
}
- override fun isBypassEnabled() = keyguardBypassController.bypassEnabled
+ override val isBypassEnabled: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val listener =
+ object : KeyguardBypassController.OnBypassStateChangedListener {
+ override fun onBypassStateChanged(isEnabled: Boolean) {
+ trySend(isEnabled)
+ }
+ }
+ keyguardBypassController.registerOnBypassStateChangedListener(listener)
+ awaitClose {
+ keyguardBypassController.unregisterOnBypassStateChangedListener(listener)
+ }
+ }
+ .stateIn(
+ applicationScope,
+ SharingStarted.Eagerly,
+ initialValue = keyguardBypassController.bypassEnabled,
+ )
companion object {
private const val TAG = "DeviceEntryRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 5612c9a..e96e318 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -106,5 +106,5 @@
* authentication challenge via face unlock or fingerprint sensor can automatically bypass the
* lock screen.
*/
- fun isBypassEnabled() = repository.isBypassEnabled()
+ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index d6987629..ac012f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -29,8 +29,10 @@
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Handles key events arriving when the keyguard is showing or device is dozing. */
+@ExperimentalCoroutinesApi
@SysUISingleton
class KeyguardKeyEventInteractor
@Inject
@@ -56,7 +58,11 @@
if (event.handleAction()) {
when (event.keyCode) {
KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
- KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
+ KeyEvent.KEYCODE_SPACE,
+ KeyEvent.KEYCODE_ENTER ->
+ if (isDeviceAwake()) {
+ return collapseShadeLockedOrShowPrimaryBouncer()
+ }
}
}
return false
@@ -92,16 +98,24 @@
(statusBarStateController.state != StatusBarState.SHADE) &&
statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
if (shouldUnlockOnMenuPressed) {
- shadeController.animateCollapseShadeForced()
- return true
+ return collapseShadeLockedOrShowPrimaryBouncer()
}
return false
}
- private fun dispatchSpaceEvent(): Boolean {
- if (isDeviceAwake() && statusBarStateController.state != StatusBarState.SHADE) {
- shadeController.animateCollapseShadeForced()
- return true
+ private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean {
+ when (statusBarStateController.state) {
+ StatusBarState.SHADE -> return false
+ StatusBarState.SHADE_LOCKED -> {
+ shadeController.animateCollapseShadeForced()
+ return true
+ }
+ StatusBarState.KEYGUARD -> {
+ if (!statusBarKeyguardViewManager.primaryBouncerIsShowing()) {
+ statusBarKeyguardViewManager.showPrimaryBouncer(true)
+ return true
+ }
+ }
}
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 2b56d0c..d08d040 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -239,6 +239,8 @@
protected void onDestroy() {
super.onDestroy();
if (mDialog != null) {
+ mDialog.setOnDismissListener(null);
+ mDialog.setOnCancelListener(null);
mDialog.dismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 584456d..91b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -42,6 +42,7 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,7 +52,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
-import javax.inject.Inject
/**
* Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
@@ -142,7 +142,7 @@
// When the device becomes unlocked in Lockscreen, go to Gone if
// bypass is enabled.
renderedScenes.contains(SceneKey.Lockscreen) ->
- if (deviceEntryInteractor.isBypassEnabled()) {
+ if (deviceEntryInteractor.isBypassEnabled.value) {
SceneKey.Gone to
"device unlocked in Lockscreen scene with bypass"
} else {
@@ -179,36 +179,34 @@
}
applicationScope.launch {
- powerInteractor.isAsleep
- .collect { isAsleep ->
- if (isAsleep) {
- switchToScene(
- targetSceneKey = SceneKey.Lockscreen,
- loggingReason = "device is starting to sleep",
- )
- } else {
- val authMethod = authenticationInteractor.getAuthenticationMethod()
- val isUnlocked = deviceEntryInteractor.isUnlocked.value
- when {
- authMethod == AuthenticationMethodModel.None -> {
- switchToScene(
- targetSceneKey = SceneKey.Gone,
- loggingReason =
- "device is starting to wake up while auth method is" +
- " none",
- )
- }
- authMethod.isSecure && isUnlocked -> {
- switchToScene(
- targetSceneKey = SceneKey.Gone,
- loggingReason =
- "device is starting to wake up while unlocked with a" +
- " secure auth method",
- )
- }
+ powerInteractor.isAsleep.collect { isAsleep ->
+ if (isAsleep) {
+ switchToScene(
+ targetSceneKey = SceneKey.Lockscreen,
+ loggingReason = "device is starting to sleep",
+ )
+ } else {
+ val authMethod = authenticationInteractor.getAuthenticationMethod()
+ val isUnlocked = deviceEntryInteractor.isUnlocked.value
+ when {
+ authMethod == AuthenticationMethodModel.None -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason =
+ "device is starting to wake up while auth method is" + " none",
+ )
+ }
+ authMethod.isSecure && isUnlocked -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason =
+ "device is starting to wake up while unlocked with a" +
+ " secure auth method",
+ )
}
}
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 4bc93a8..2e45353 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -61,12 +61,17 @@
data class Swipe(
/** The direction of the swipe. */
val direction: Direction,
+ /**
+ * The edge from which the swipe originated or `null`, if the swipe didn't start close to an
+ * edge.
+ */
+ val fromEdge: Edge? = null,
/** The number of pointers that were used (for example, one or two fingers). */
val pointerCount: Int = 1,
) : UserAction
/** The user has hit the back button or performed the back navigation gesture. */
- object Back : UserAction
+ data object Back : UserAction
}
/** Enumerates all known "cardinal" directions for user actions. */
@@ -76,3 +81,11 @@
RIGHT,
DOWN,
}
+
+/** Enumerates all known edges from which a swipe can start. */
+enum class Edge {
+ LEFT,
+ TOP,
+ RIGHT,
+ BOTTOM,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index d24f9d8..77b0958 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,6 +18,8 @@
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -113,7 +115,16 @@
fadeIn(view, ANIMATION_DURATION_LENGTH, 0);
}
+ public static void fadeIn(final View view, Runnable endRunnable) {
+ fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, endRunnable);
+ }
+
public static void fadeIn(final View view, long duration, int delay) {
+ fadeIn(view, duration, delay, /* endRunnable= */ null);
+ }
+
+ public static void fadeIn(final View view, long duration, int delay,
+ @Nullable Runnable endRunnable) {
view.animate().cancel();
if (view.getVisibility() == View.INVISIBLE) {
view.setAlpha(0.0f);
@@ -124,7 +135,7 @@
.setDuration(duration)
.setStartDelay(delay)
.setInterpolator(Interpolators.ALPHA_IN)
- .withEndAction(null);
+ .withEndAction(endRunnable);
if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
view.animate().withLayer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index f616b91..3a4ad0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -51,6 +51,7 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
+import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import com.android.app.animation.Interpolators;
@@ -959,12 +960,17 @@
}
public void setDozing(boolean dozing, boolean fade, long delay) {
+ setDozing(dozing, fade, delay, /* onChildCompleted= */ null);
+ }
+
+ public void setDozing(boolean dozing, boolean fade, long delay,
+ @Nullable Runnable endRunnable) {
mDozer.setDozing(f -> {
mDozeAmount = f;
updateDecorColor();
updateIconColor();
updateAllowAnimation();
- }, dozing, fade, delay, this);
+ }, dozing, fade, delay, this, endRunnable);
}
private void updateAllowAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
index 167efc7..dc0eb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -24,6 +24,8 @@
import android.view.View;
import android.widget.ImageView;
+import androidx.annotation.Nullable;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -81,6 +83,11 @@
public void setDozing(Consumer<Float> listener, boolean dozing,
boolean animate, long delay, View view) {
+ setDozing(listener, dozing, animate, delay, view, /* endRunnable= */ null);
+ }
+
+ public void setDozing(Consumer<Float> listener, boolean dozing,
+ boolean animate, long delay, View view, @Nullable Runnable endRunnable) {
if (animate) {
startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dozing,
delay,
@@ -89,6 +96,9 @@
@Override
public void onAnimationEnd(Animator animation) {
view.setTag(DOZE_ANIMATOR_TAG, null);
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
}
@Override
@@ -102,6 +112,9 @@
animator.cancel();
}
listener.accept(dozing ? 1f : 0f);
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 733d774..8561869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
import com.android.systemui.statusbar.notification.icon.IconManager;
@@ -95,6 +96,7 @@
CoordinatorsModule.class,
KeyguardNotificationVisibilityProviderModule.class,
ShadeEventsModule.class,
+ NotificationDataLayerModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
NotificationListViewModelModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
new file mode 100644
index 0000000..5435fb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.notification.data
+
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardStateRepositoryModule
+import dagger.Module
+
+@Module(includes = [NotificationsKeyguardStateRepositoryModule::class])
+interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
new file mode 100644
index 0000000..cf03d1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.notification.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** View-states pertaining to notifications on the keyguard. */
+interface NotificationsKeyguardViewStateRepository {
+ /** Are notifications fully hidden from view? */
+ val areNotificationsFullyHidden: Flow<Boolean>
+
+ /** Is a pulse expansion occurring? */
+ val isPulseExpanding: Flow<Boolean>
+}
+
+@Module
+interface NotificationsKeyguardStateRepositoryModule {
+ @Binds
+ fun bindImpl(
+ impl: NotificationsKeyguardViewStateRepositoryImpl
+ ): NotificationsKeyguardViewStateRepository
+}
+
+@SysUISingleton
+class NotificationsKeyguardViewStateRepositoryImpl
+@Inject
+constructor(
+ wakeUpCoordinator: NotificationWakeUpCoordinator,
+) : NotificationsKeyguardViewStateRepository {
+ override val areNotificationsFullyHidden: Flow<Boolean> = conflatedCallbackFlow {
+ val listener =
+ object : NotificationWakeUpCoordinator.WakeUpListener {
+ override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
+ trySend(isFullyHidden)
+ }
+ }
+ trySend(wakeUpCoordinator.notificationsFullyHidden)
+ wakeUpCoordinator.addListener(listener)
+ awaitClose { wakeUpCoordinator.removeListener(listener) }
+ }
+
+ override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
+ val listener =
+ object : NotificationWakeUpCoordinator.WakeUpListener {
+ override fun onPulseExpansionChanged(expandingChanged: Boolean) {
+ trySend(expandingChanged)
+ }
+ }
+ trySend(wakeUpCoordinator.isPulseExpanding())
+ wakeUpCoordinator.addListener(listener)
+ awaitClose { wakeUpCoordinator.removeListener(listener) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
new file mode 100644
index 0000000..87b8e55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.notification.domain.interactor
+
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Domain logic pertaining to notifications on the keyguard. */
+class NotificationsKeyguardInteractor
+@Inject
+constructor(
+ repository: NotificationsKeyguardViewStateRepository,
+) {
+ /** Is a pulse expansion occurring? */
+ val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding
+
+ /** Are notifications fully hidden from view? */
+ val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index a77e67b..805a4db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -26,26 +26,21 @@
import androidx.annotation.ColorInt
import androidx.annotation.VisibleForTesting
import androidx.collection.ArrayMap
-import com.android.app.animation.Interpolators
import com.android.internal.statusbar.StatusBarIcon
import com.android.internal.util.ContrastColorUtil
import com.android.settingslib.Utils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.Flags.NEW_AOD_TRANSITION
import com.android.systemui.flags.ViewRefactorFlag
import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -60,6 +55,7 @@
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -79,9 +75,9 @@
@Inject
constructor(
private val context: Context,
- private val statusBarStateController: StatusBarStateController,
private val wakeUpCoordinator: NotificationWakeUpCoordinator,
private val bypassController: KeyguardBypassController,
+ private val configurationController: ConfigurationController,
private val mediaManager: NotificationMediaManager,
notificationListener: NotificationListener,
private val dozeParameters: DozeParameters,
@@ -89,7 +85,7 @@
private val bubblesOptional: Optional<Bubbles>,
demoModeController: DemoModeController,
darkIconDispatcher: DarkIconDispatcher,
- private val featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlagsClassic,
private val statusBarWindowController: StatusBarWindowController,
private val screenOffAnimationController: ScreenOffAnimationController,
private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
@@ -98,14 +94,12 @@
) :
NotificationIconAreaController,
DarkIconDispatcher.DarkReceiver,
- StatusBarStateController.StateListener,
NotificationWakeUpCoordinator.WakeUpListener,
DemoMode {
private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
- private val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
private val tintAreas = ArrayList<Rect>()
private var iconSize = 0
@@ -119,7 +113,6 @@
private var aodBindJob: DisposableHandle? = null
private var aodIconAppearTranslation = 0
private var aodIconTint = 0
- private var aodIconsVisible = false
private var showLowPriority = true
@VisibleForTesting
@@ -132,7 +125,6 @@
}
init {
- statusBarStateController.addCallback(this)
wakeUpCoordinator.addListener(this)
demoModeController.addCallback(this)
notificationListener.addNotificationSettingsListener(settingsListener)
@@ -160,8 +152,11 @@
NotificationIconContainerViewBinder.bind(
aodIcons,
aodIconsViewModel,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
)
- updateAodIconsVisibility(animate = false, forceUpdate = changed)
if (changed) {
updateAodNotificationIcons()
}
@@ -176,6 +171,10 @@
NotificationIconContainerViewBinder.bind(
icons,
shelfIconsViewModel,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
)
shelfIcons = icons
}
@@ -249,20 +248,8 @@
notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
}
- override fun onDozingChanged(isDozing: Boolean) {
- if (aodIcons == null) {
- return
- }
- val animate = (dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking)
- aodIcons!!.setDozing(isDozing, animate, 0)
- }
-
override fun setAnimationsEnabled(enabled: Boolean) = unsupported
- override fun onStateChanged(newState: Int) {
- updateAodIconsVisibility(animate = false, forceUpdate = false)
- }
-
override fun onThemeChanged() {
reloadAodColor()
updateAodIconColors()
@@ -272,53 +259,11 @@
return if (aodIcons == null) 0 else aodIcons!!.height
}
- @VisibleForTesting
- fun appearAodIcons() {
- if (aodIcons == null) {
- return
- }
- if (screenOffAnimationController.shouldAnimateAodIcons()) {
- if (!statusViewMigrated) {
- aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
- }
- aodIcons!!.alpha = 0f
- animateInAodIconTranslation()
- aodIcons!!
- .animate()
- .alpha(1f)
- .setInterpolator(Interpolators.LINEAR)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start()
- } else {
- aodIcons!!.alpha = 1.0f
- if (!statusViewMigrated) {
- aodIcons!!.translationY = 0f
- }
- }
- }
-
override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- var animate = true
- if (!bypassController.bypassEnabled) {
- animate = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
- if (!featureFlags.isEnabled(NEW_AOD_TRANSITION)) {
- // We only want the appear animations to happen when the notifications get fully
- // hidden,
- // since otherwise the unhide animation overlaps
- animate = animate and isFullyHidden
- }
- }
- updateAodIconsVisibility(animate, false /* force */)
updateAodNotificationIcons()
updateAodIconColors()
}
- override fun onPulseExpansionChanged(expandingChanged: Boolean) {
- if (expandingChanged) {
- updateAodIconsVisibility(animate = true, forceUpdate = false)
- }
- }
-
override fun demoCommands(): List<String> {
val commands = ArrayList<String>()
commands.add(DemoMode.COMMAND_NOTIFICATIONS)
@@ -352,6 +297,10 @@
NotificationIconContainerViewBinder.bind(
notificationIcons!!,
statusBarIconsViewModel,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
)
}
@@ -602,17 +551,6 @@
v.setDecorColor(tint)
}
- private fun animateInAodIconTranslation() {
- if (!statusViewMigrated) {
- aodIcons!!
- .animate()
- .setInterpolator(Interpolators.DECELERATE_QUINT)
- .translationY(0f)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start()
- }
- }
-
private fun reloadAodColor() {
aodIconTint =
Utils.getColorAttrDefaultColor(
@@ -635,69 +573,7 @@
}
}
- private fun updateAodIconsVisibility(animate: Boolean, forceUpdate: Boolean) {
- if (aodIcons == null) {
- return
- }
- var visible = (bypassController.bypassEnabled || wakeUpCoordinator.notificationsFullyHidden)
-
- // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
- // playing, in which case we want them to be visible since we're animating in the AOD UI and
- // will be switching to KEYGUARD shortly.
- if (
- statusBarStateController.state != StatusBarState.KEYGUARD &&
- !screenOffAnimationController.shouldShowAodIconsWhenShade()
- ) {
- visible = false
- }
- if (visible && wakeUpCoordinator.isPulseExpanding() && !bypassController.bypassEnabled) {
- visible = false
- }
- if (aodIconsVisible != visible || forceUpdate) {
- aodIconsVisible = visible
- aodIcons!!.animate().cancel()
- if (animate) {
- if (featureFlags.isEnabled(NEW_AOD_TRANSITION)) {
- // Let's make sure the icon are translated to 0, since we cancelled it above
- animateInAodIconTranslation()
- if (aodIconsVisible) {
- CrossFadeHelper.fadeIn(aodIcons)
- } else {
- CrossFadeHelper.fadeOut(aodIcons)
- }
- } else {
- val wasFullyInvisible = aodIcons!!.visibility != View.VISIBLE
- if (aodIconsVisible) {
- if (wasFullyInvisible) {
- // No fading here, let's just appear the icons instead!
- aodIcons!!.visibility = View.VISIBLE
- aodIcons!!.alpha = 1.0f
- appearAodIcons()
- } else {
- // Let's make sure the icon are translated to 0, since we cancelled it
- // above
- animateInAodIconTranslation()
- // We were fading out, let's fade in instead
- CrossFadeHelper.fadeIn(aodIcons)
- }
- } else {
- // Let's make sure the icon are translated to 0, since we cancelled it above
- animateInAodIconTranslation()
- CrossFadeHelper.fadeOut(aodIcons)
- }
- }
- } else {
- aodIcons!!.alpha = 1.0f
- if (!statusViewMigrated) {
- aodIcons!!.translationY = 0f
- }
- aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE
- }
- }
- }
-
companion object {
- private const val AOD_ICONS_APPEAR_DURATION: Long = 200
@ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
val unsupported: Nothing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index f8ff3e3..0d2f00a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -15,12 +15,27 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
+import android.content.res.Resources
+import android.view.View
+import androidx.annotation.DimenRes
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.util.kotlin.stateFlow
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
@@ -28,11 +43,144 @@
fun bind(
view: NotificationIconContainer,
viewModel: NotificationIconContainerViewModel,
+ configurationController: ConfigurationController,
+ dozeParameters: DozeParameters,
+ featureFlags: FeatureFlagsClassic,
+ screenOffAnimationController: ScreenOffAnimationController,
): DisposableHandle {
return view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
+ launch {
+ viewModel.isDozing.collect { (isDozing, animate) ->
+ val animateIfNotBlanking = animate && !dozeParameters.displayNeedsBlanking
+ view.setDozing(isDozing, animateIfNotBlanking, /* delay= */ 0) {
+ viewModel.completeDozeAnimation()
+ }
+ }
+ }
+ // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+ // view-binder
+ launch {
+ val iconAppearTranslation =
+ view.resources.getConfigAwareDimensionPixelSize(
+ this,
+ configurationController,
+ R.dimen.shelf_appear_translation,
+ )
+ bindVisibility(
+ viewModel,
+ view,
+ featureFlags,
+ screenOffAnimationController,
+ iconAppearTranslation,
+ ) {
+ viewModel.completeVisibilityAnimation()
+ }
+ }
}
}
}
+ private suspend fun bindVisibility(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ featureFlags: FeatureFlagsClassic,
+ screenOffAnimationController: ScreenOffAnimationController,
+ iconAppearTranslation: StateFlow<Int>,
+ onAnimationEnd: () -> Unit,
+ ) {
+ val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
+ viewModel.isVisible.collect { (isVisible, animate) ->
+ view.animate().cancel()
+ when {
+ !animate -> {
+ view.alpha = 1f
+ if (!statusViewMigrated) {
+ view.translationY = 0f
+ }
+ view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ }
+ featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
+ animateInIconTranslation(view, statusViewMigrated)
+ if (isVisible) {
+ CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ } else {
+ CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ }
+ }
+ !isVisible -> {
+ // Let's make sure the icon are translated to 0, since we cancelled it above
+ animateInIconTranslation(view, statusViewMigrated)
+ CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ }
+ view.visibility != View.VISIBLE -> {
+ // No fading here, let's just appear the icons instead!
+ view.visibility = View.VISIBLE
+ view.alpha = 1f
+ appearIcons(
+ view,
+ animate = screenOffAnimationController.shouldAnimateAodIcons(),
+ iconAppearTranslation.value,
+ statusViewMigrated,
+ )
+ onAnimationEnd()
+ }
+ else -> {
+ // Let's make sure the icons are translated to 0, since we cancelled it above
+ animateInIconTranslation(view, statusViewMigrated)
+ // We were fading out, let's fade in instead
+ CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ }
+ }
+ }
+ }
+
+ private fun appearIcons(
+ view: View,
+ animate: Boolean,
+ iconAppearTranslation: Int,
+ statusViewMigrated: Boolean,
+ ) {
+ if (animate) {
+ if (!statusViewMigrated) {
+ view.translationY = -iconAppearTranslation.toFloat()
+ }
+ view.alpha = 0f
+ animateInIconTranslation(view, statusViewMigrated)
+ view
+ .animate()
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .start()
+ } else {
+ view.alpha = 1.0f
+ if (!statusViewMigrated) {
+ view.translationY = 0f
+ }
+ }
+ }
+
+ private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) {
+ if (!statusViewMigrated) {
+ view
+ .animate()
+ .setInterpolator(Interpolators.DECELERATE_QUINT)
+ .translationY(0f)
+ .setDuration(AOD_ICONS_APPEAR_DURATION)
+ .start()
+ }
+ }
+
+ private const val AOD_ICONS_APPEAR_DURATION: Long = 200
}
+
+fun Resources.getConfigAwareDimensionPixelSize(
+ scope: CoroutineScope,
+ configurationController: ConfigurationController,
+ @DimenRes id: Int,
+): StateFlow<Int> =
+ scope.stateFlow(
+ changedSignals = configurationController.onDensityOrFontScaleChanged,
+ getValue = { getDimensionPixelSize(id) }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 90507fc..3289a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,19 +15,48 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed on the always-on display. */
+@SysUISingleton
class NotificationIconContainerAlwaysOnDisplayViewModel
@Inject
constructor(
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val dozeParameters: DozeParameters,
+ private val featureFlags: FeatureFlagsClassic,
keyguardInteractor: KeyguardInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
) : NotificationIconContainerViewModel {
+
+ private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+ private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
override val animationsEnabled: Flow<Boolean> =
combine(
shadeInteractor.isShadeTouchable,
@@ -35,4 +64,97 @@
) { panelTouchesEnabled, isKeyguardVisible ->
panelTouchesEnabled && isKeyguardVisible
}
+
+ override val isDozing: Flow<AnimatedValue<Boolean>> =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ // Determine if we're dozing based on the most recent transition
+ .map { step: TransitionStep ->
+ val isDozing = step.to == KeyguardState.AOD || step.to == KeyguardState.DOZING
+ isDozing to step
+ }
+ // Only emit changes based on whether we've started or stopped dozing
+ .distinctUntilChanged { (wasDozing, _), (isDozing, _) -> wasDozing != isDozing }
+ // Determine whether we need to animate
+ .map { (isDozing, step) ->
+ val animate = step.to == KeyguardState.AOD || step.from == KeyguardState.AOD
+ AnimatableEvent(isDozing, animate)
+ }
+ .distinctUntilChanged()
+ .toAnimatedValueFlow(completionEvents = onDozeAnimationComplete)
+
+ override val isVisible: Flow<AnimatedValue<Boolean>> =
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState.map { it != KeyguardState.GONE },
+ deviceEntryInteractor.isBypassEnabled,
+ areNotifsFullyHiddenAnimated(),
+ isPulseExpandingAnimated(),
+ ) {
+ onKeyguard: Boolean,
+ bypassEnabled: Boolean,
+ (notifsFullyHidden: Boolean, isAnimatingHide: Boolean),
+ (pulseExpanding: Boolean, isAnimatingPulse: Boolean),
+ ->
+ val isAnimating = isAnimatingHide || isAnimatingPulse
+ when {
+ // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
+ // animation is playing, in which case we want them to be visible if we're
+ // animating in the AOD UI and will be switching to KEYGUARD shortly.
+ !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
+ AnimatedValue(false, isAnimating = false)
+ // If we're bypassing, then we're visible
+ bypassEnabled -> AnimatedValue(true, isAnimating)
+ // If we are pulsing (and not bypassing), then we are hidden
+ pulseExpanding -> AnimatedValue(false, isAnimating)
+ // If notifs are fully gone, then we're visible
+ notifsFullyHidden -> AnimatedValue(true, isAnimating)
+ // Otherwise, we're hidden
+ else -> AnimatedValue(false, isAnimating)
+ }
+ }
+ .distinctUntilChanged()
+
+ override fun completeDozeAnimation() {
+ onDozeAnimationComplete.tryEmit(Unit)
+ }
+
+ override fun completeVisibilityAnimation() {
+ onVisAnimationComplete.tryEmit(Unit)
+ }
+
+ /** Is there an expanded pulse, are we animating in response? */
+ private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
+ return notificationsKeyguardInteractor.isPulseExpanding
+ .pairwise(initialValue = null)
+ // If pulsing changes, start animating, unless it's the first emission
+ .map { (prev, expanding) ->
+ AnimatableEvent(expanding!!, startAnimating = prev != null)
+ }
+ .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ }
+
+ /** Are notifications completely hidden from view, are we animating in response? */
+ private fun areNotifsFullyHiddenAnimated(): Flow<AnimatedValue<Boolean>> {
+ return notificationsKeyguardInteractor.areNotificationsFullyHidden
+ .pairwise(initialValue = null)
+ .sample(deviceEntryInteractor.isBypassEnabled) { (prev, fullyHidden), bypassEnabled ->
+ val animate =
+ when {
+ // Don't animate for the first value
+ prev == null -> false
+ // Always animate if bypass is enabled.
+ bypassEnabled -> true
+ // If we're not bypassing and we're not going to AOD, then we're not
+ // animating.
+ !dozeParameters.alwaysOn -> false
+ // Don't animate when going to AOD if the display needs blanking.
+ dozeParameters.displayNeedsBlanking -> false
+ // We only want the appear animations to happen when the notifications
+ // get fully hidden, since otherwise the un-hide animation overlaps.
+ featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
+ else -> fullyHidden!!
+ }
+ AnimatableEvent(fullyHidden!!, animate)
+ }
+ .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index 49f262d..c44a2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -15,12 +15,18 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import com.android.systemui.util.ui.AnimatedValue
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
/** View-model for the overflow row of notification icons displayed in the notification shade. */
class NotificationIconContainerShelfViewModel @Inject constructor() :
NotificationIconContainerViewModel {
override val animationsEnabled: Flow<Boolean> = flowOf(true)
+ override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
+ override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
+ override fun completeDozeAnimation() {}
+ override fun completeVisibilityAnimation() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index ee57b78..035687a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -17,9 +17,11 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.ui.AnimatedValue
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
/** View-model for the row of notification icons displayed in the status bar, */
class NotificationIconContainerStatusBarViewModel
@@ -35,4 +37,9 @@
) { panelTouchesEnabled, isKeyguardShowing ->
panelTouchesEnabled && !isKeyguardShowing
}
+
+ override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
+ override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
+ override fun completeDozeAnimation() {}
+ override fun completeVisibilityAnimation() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
index 6f8ce4b..65eb220 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import com.android.systemui.util.ui.AnimatedValue
import kotlinx.coroutines.flow.Flow
/**
@@ -24,4 +25,22 @@
interface NotificationIconContainerViewModel {
/** Are changes to the icon container animated? */
val animationsEnabled: Flow<Boolean>
+
+ /** Should icons be rendered in "dozing" mode? */
+ val isDozing: Flow<AnimatedValue<Boolean>>
+
+ /** Is the icon container visible? */
+ val isVisible: Flow<AnimatedValue<Boolean>>
+
+ /**
+ * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
+ * property was `true`, calling this method will update it to `false.
+ */
+ fun completeDozeAnimation()
+
+ /**
+ * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
+ * property was `true`, calling this method will update it to `false.
+ */
+ fun completeVisibilityAnimation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index 42c80ed..40897da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -42,9 +42,8 @@
import android.widget.TextView;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.Compile;
@@ -76,7 +75,9 @@
final StatusBarNotification sbn,
final NotificationEntry entry,
final ExpandableNotificationRow row,
- final AssistantFeedbackController controller) {
+ final AssistantFeedbackController controller,
+ final IStatusBarService statusBarService,
+ final NotificationGutsManager notificationGutsManager) {
mPkg = sbn.getPackageName();
mPm = pm;
mEntry = entry;
@@ -84,8 +85,8 @@
mRanking = entry.getRanking();
mFeedbackController = controller;
mAppName = mPkg;
- mStatusBarService = Dependency.get(IStatusBarService.class);
- mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
+ mStatusBarService = statusBarService;
+ mNotificationGutsManager = notificationGutsManager;
bindHeader();
bindPrompt();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index dc318a3..9e9116b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -44,6 +44,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
@@ -99,6 +100,7 @@
// Dependencies:
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final StatusBarStateController mStatusBarStateController;
+ private final IStatusBarService mStatusBarService;
private final DeviceProvisionedController mDeviceProvisionedController;
private final AssistantFeedbackController mAssistantFeedbackController;
@@ -152,6 +154,7 @@
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
NotificationLockscreenUserManager notificationLockscreenUserManager,
StatusBarStateController statusBarStateController,
+ IStatusBarService statusBarService,
DeviceProvisionedController deviceProvisionedController,
MetricsLogger metricsLogger,
HeadsUpManager headsUpManager,
@@ -177,6 +180,7 @@
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mLockscreenUserManager = notificationLockscreenUserManager;
mStatusBarStateController = statusBarStateController;
+ mStatusBarService = statusBarService;
mDeviceProvisionedController = deviceProvisionedController;
mMetricsLogger = metricsLogger;
mHeadsUpManager = headsUpManager;
@@ -358,7 +362,8 @@
PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(mContext,
userHandle.getIdentifier());
- feedbackInfo.bindGuts(pmUser, sbn, row.getEntry(), row, mAssistantFeedbackController);
+ feedbackInfo.bindGuts(pmUser, sbn, row.getEntry(), row, mAssistantFeedbackController,
+ mStatusBarService, this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 7cbaf63..b15c0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -33,6 +33,7 @@
import android.view.ViewGroup;
import android.view.animation.Interpolator;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
@@ -624,12 +625,32 @@
}
public void setDozing(boolean dozing, boolean fade, long delay) {
+ setDozing(dozing, fade, delay, /* endRunnable= */ null);
+ }
+
+ public void setDozing(boolean dozing, boolean fade, long delay,
+ @Nullable Runnable endRunnable) {
mDozing = dozing;
mDisallowNextAnimation |= !fade;
- for (int i = 0; i < getChildCount(); i++) {
+ final int childCount = getChildCount();
+ // Track all the child invocations of setDozing, invoking the top-level endRunnable once
+ // they have all completed.
+ final Runnable onChildCompleted = endRunnable == null ? null : new Runnable() {
+ private int mPendingCallbacks = childCount;
+
+ @Override
+ public void run() {
+ if (--mPendingCallbacks == 0) {
+ endRunnable.run();
+ }
+ }
+ };
+ for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
if (view instanceof StatusBarIconView) {
- ((StatusBarIconView) view).setDozing(dozing, fade, delay);
+ ((StatusBarIconView) view).setDozing(dozing, fade, delay, onChildCompleted);
+ } else if (onChildCompleted != null) {
+ onChildCompleted.run();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
new file mode 100644
index 0000000..21acfb4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.policy
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A [Flow] that emits whenever screen density or font scale has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged
+ */
+val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit>
+ get() =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ trySend(Unit)
+ }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 47d505e..83ff789 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -18,19 +18,24 @@
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.SystemClockImpl
-import kotlinx.coroutines.CoroutineStart
import java.util.concurrent.atomic.AtomicReference
+import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
-import kotlin.math.max
/**
* Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -44,8 +49,7 @@
var previousValue: Any? = noVal
collect { newVal ->
if (previousValue != noVal) {
- @Suppress("UNCHECKED_CAST")
- emit(transform(previousValue as T, newVal))
+ @Suppress("UNCHECKED_CAST") emit(transform(previousValue as T, newVal))
}
previousValue = newVal
}
@@ -60,13 +64,11 @@
fun <T, R> Flow<T>.pairwiseBy(
initialValue: T,
transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> =
- onStart { emit(initialValue) }.pairwiseBy(transform)
+): Flow<R> = onStart { emit(initialValue) }.pairwiseBy(transform)
/**
* Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
*
- *
* The output of [getInitialValue] will be used as the "old" value for the first emission. As
* opposed to the initial value in the above [pairwiseBy], [getInitialValue] can do some work before
* returning the initial value.
@@ -76,8 +78,7 @@
fun <T, R> Flow<T>.pairwiseBy(
getInitialValue: suspend () -> T,
transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> =
- onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+): Flow<R> = onStart { emit(getInitialValue()) }.pairwiseBy(transform)
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
@@ -88,8 +89,8 @@
fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
/**
- * Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue]
- * will be used as the "old" value for the first emission.
+ * Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue] will
+ * be used as the "old" value for the first emission.
*
* Useful for code that needs to compare the current value to the previous value.
*/
@@ -102,9 +103,9 @@
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
* [transform].
*
- * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause
- * a change event to be emitted that contains no removals, and all elements from that first [Set]
- * as additions.
+ * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause a
+ * change event to be emitted that contains no removals, and all elements from that first [Set] as
+ * additions.
*
* If [emitFirstEvent] is `false`, then the first emission is ignored and no changes are emitted
* until a second [Set] has been emitted from the upstream [Flow].
@@ -112,22 +113,23 @@
fun <T, R> Flow<Set<T>>.setChangesBy(
transform: suspend (removed: Set<T>, added: Set<T>) -> R,
emitFirstEvent: Boolean = true,
-): Flow<R> = (if (emitFirstEvent) onStart { emit(emptySet()) } else this)
- .distinctUntilChanged()
- .pairwiseBy { old: Set<T>, new: Set<T> ->
- // If an element was present in the old set, but not the new one, then it was removed
- val removed = old - new
- // If an element is present in the new set, but on the old one, then it was added
- val added = new - old
- transform(removed, added)
- }
+): Flow<R> =
+ (if (emitFirstEvent) onStart { emit(emptySet()) } else this)
+ .distinctUntilChanged()
+ .pairwiseBy { old: Set<T>, new: Set<T> ->
+ // If an element was present in the old set, but not the new one, then it was removed
+ val removed = old - new
+ // If an element is present in the new set, but on the old one, then it was added
+ val added = new - old
+ transform(removed, added)
+ }
/**
* Returns a new [Flow] that produces the [Set] changes between each emission from [this].
*
- * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause
- * a change event to be emitted that contains no removals, and all elements from that first [Set]
- * as additions.
+ * If [emitFirstEvent] is `true`, then the first [Set] emitted from the upstream [Flow] will cause a
+ * change event to be emitted that contains no removals, and all elements from that first [Set] as
+ * additions.
*
* If [emitFirstEvent] is `false`, then the first emission is ignored and no changes are emitted
* until a second [Set] has been emitted from the upstream [Flow].
@@ -153,14 +155,11 @@
coroutineScope {
val noVal = Any()
val sampledRef = AtomicReference(noVal)
- val job = launch(Dispatchers.Unconfined) {
- other.collect { sampledRef.set(it) }
- }
+ val job = launch(Dispatchers.Unconfined) { other.collect { sampledRef.set(it) } }
collect {
val sampled = sampledRef.get()
if (sampled != noVal) {
- @Suppress("UNCHECKED_CAST")
- emit(transform(it, sampled as B))
+ @Suppress("UNCHECKED_CAST") emit(transform(it, sampled as B))
}
}
job.cancel()
@@ -181,7 +180,6 @@
* latest value is emitted.
*
* Example:
- *
* ```kotlin
* flow {
* emit(1) // t=0ms
@@ -210,7 +208,6 @@
* during this period, only The latest value is emitted.
*
* Example:
- *
* ```kotlin
* flow {
* emit(1) // t=0ms
@@ -248,10 +245,11 @@
// We create delayJob to allow cancellation during the delay period
delayJob = launch {
delay(timeUntilNextEmit)
- sendJob = outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
- send(it)
- previousEmitTimeMs = clock.elapsedRealtime()
- }
+ sendJob =
+ outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ send(it)
+ previousEmitTimeMs = clock.elapsedRealtime()
+ }
}
} else {
send(it)
@@ -259,4 +257,15 @@
}
}
}
-}
\ No newline at end of file
+}
+
+/**
+ * Returns a [StateFlow] launched in the surrounding [CoroutineScope]. This [StateFlow] gets its
+ * value by invoking [getValue] whenever an event is emitted from [changedSignals]. It will also
+ * immediately invoke [getValue] to establish its initial value.
+ */
+inline fun <T> CoroutineScope.stateFlow(
+ changedSignals: Flow<Unit>,
+ crossinline getValue: () -> T,
+): StateFlow<T> =
+ changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
diff --git a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
new file mode 100644
index 0000000..51d2afa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.util.ui
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
+ * [value] [isAnimating] in the UI.
+ */
+data class AnimatedValue<T>(
+ val value: T,
+ val isAnimating: Boolean,
+)
+
+/**
+ * An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
+ * whether or not this event should start an animation.
+ */
+data class AnimatableEvent<T>(
+ val value: T,
+ val startAnimating: Boolean,
+)
+
+/**
+ * Returns a [Flow] that tracks an [AnimatedValue] state. The input [Flow] is used to update the
+ * [AnimatedValue.value], as well as [AnimatedValue.isAnimating] if the event's
+ * [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
+ * [AnimatedValue.isAnimating] will flip to `false`.
+ */
+fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(
+ completionEvents: Flow<Any?>,
+): Flow<AnimatedValue<T>> = transformLatest { (value, startAnimating) ->
+ emit(AnimatedValue(value, isAnimating = startAnimating))
+ if (startAnimating) {
+ // Wait for a completion now that we've started animating
+ completionEvents
+ .map { Unit } // replace the event so that it's never `null`
+ .firstOrNull() // `null` indicates an empty flow
+ // emit the new state if the flow was not empty.
+ ?.run { emit(AnimatedValue(value, isAnimating = false)) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index c1be44a..167e341 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -81,7 +81,7 @@
@get:Provides val notificationStackSizeCalculator: NotificationStackSizeCalculator = mock(),
@get:Provides val notificationWakeUpCoordinator: NotificationWakeUpCoordinator = mock(),
@get:Provides val screenLifecycle: ScreenLifecycle = mock(),
- @get:Provides val screenOffAnimController: ScreenOffAnimationController = mock(),
+ @get:Provides val screenOffAnimationController: ScreenOffAnimationController = mock(),
@get:Provides val scrimController: ScrimController = mock(),
@get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(),
@get:Provides val statusBarWindowController: StatusBarWindowController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 989164e..3b8e02f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -20,7 +20,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.animation.LayoutTransition;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -69,7 +68,6 @@
@Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
@Mock protected FrameLayout mMediaHostContainer;
- @Mock protected LayoutTransition mMediaLayoutTransition;
@Before
public void setup() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 9a908d7..948942f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,19 +16,24 @@
package com.android.keyguard;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.animation.LayoutTransition;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import com.android.app.animation.Interpolators;
+import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.plugins.ClockConfig;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.res.R;
@@ -39,6 +44,8 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import java.lang.reflect.Field;
+
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner.class)
@@ -142,19 +149,7 @@
}
@Test
- public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() {
- when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
- mMediaHostContainer);
-
- mController.onInit();
-
- ArgumentCaptor<View.OnLayoutChangeListener> captor =
- ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
- verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
- }
-
- @Test
- public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() {
+ public void clockSwitchHeightChanged_animatesMediaHostContainer() {
when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
mMediaHostContainer);
@@ -167,6 +162,10 @@
// Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
// Below here is the actual test.
+ ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
+ ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
+ setCompanion(spiedAnimator);
+
View.OnLayoutChangeListener listener = captor.getValue();
mController.setSplitShadeEnabled(true);
@@ -174,17 +173,20 @@
when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
when(mMediaHostContainer.getHeight()).thenReturn(200);
- when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
when(mKeyguardClockSwitch.getHeight()).thenReturn(0);
listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
0, /* oldBottom = */ 200);
- verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING);
+ verify(spiedAnimator).animateNextUpdate(mMediaHostContainer,
+ Interpolators.STANDARD, /* duration= */ 500L, /* animateChildren= */ false);
+
+ // Resets ViewHierarchyAnimator.Companion to its original value
+ setCompanion(animator);
}
@Test
- public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() {
+ public void clockSwitchHeightNotChanged_doesNotAnimateMediaOutputContainer() {
when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
mMediaHostContainer);
@@ -197,6 +199,10 @@
// Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`.
// Below here is the actual test.
+ ViewHierarchyAnimator.Companion animator = ViewHierarchyAnimator.Companion;
+ ViewHierarchyAnimator.Companion spiedAnimator = spy(animator);
+ setCompanion(spiedAnimator);
+
View.OnLayoutChangeListener listener = captor.getValue();
mController.setSplitShadeEnabled(true);
@@ -204,36 +210,24 @@
when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true);
when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE);
when(mMediaHostContainer.getHeight()).thenReturn(200);
- when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
when(mKeyguardClockSwitch.getHeight()).thenReturn(200);
listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */
0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */
0, /* oldBottom = */ 200);
- verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING);
+ verify(spiedAnimator, never()).animateNextUpdate(any(), any(), anyLong(), anyBoolean());
+
+ // Resets ViewHierarchyAnimator.Companion to its original value
+ setCompanion(animator);
}
- @Test
- public void onMediaHostContainerLayout_disablesChangingLayoutTransition() {
- when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn(
- mMediaHostContainer);
-
- mController.onInit();
-
- ArgumentCaptor<View.OnLayoutChangeListener> captor =
- ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
- verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture());
-
- // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`.
- // Below here is the actual test.
-
- View.OnLayoutChangeListener listener = captor.getValue();
-
- when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition);
-
- when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn(
- true);
- listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4);
- verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING);
+ private void setCompanion(ViewHierarchyAnimator.Companion companion) {
+ try {
+ Field field = ViewHierarchyAnimator.class.getDeclaredField("Companion");
+ field.setAccessible(true);
+ field.set(null, companion);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 58d372c..86439e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,6 +1,5 @@
package com.android.keyguard
-import android.animation.LayoutTransition
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -36,20 +35,6 @@
}
@Test
- fun mediaViewHasLayoutTransitionInDisabledState() {
- val layoutTransition = (mediaView as ViewGroup).layoutTransition
- assertThat(layoutTransition).isNotNull()
- assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING))
- .isFalse()
- assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING))
- .isFalse()
- assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse()
- assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING))
- .isFalse()
- assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse()
- }
-
- @Test
fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
val translationY = 1234f
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index ce1930a6..d61ca69 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.doze.util.BurnInHelperKt;
import com.android.systemui.dump.DumpManager;
@@ -51,6 +52,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -75,6 +77,8 @@
protected MockitoSession mStaticMockSession;
+ protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ protected @Mock BouncerInteractor mBouncerInteractor;
protected @Mock LockIconView mLockIconView;
protected @Mock AnimatedStateListDrawable mIconDrawable;
protected @Mock Context mContext;
@@ -93,6 +97,7 @@
protected @Mock AuthRippleController mAuthRippleController;
protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
protected FakeFeatureFlags mFeatureFlags;
+
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
protected LockIconViewController mUnderTest;
@@ -148,6 +153,7 @@
mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false);
+
mUnderTest = new LockIconViewController(
mStatusBarStateController,
mKeyguardUpdateMonitor,
@@ -168,7 +174,9 @@
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor,
- mContext
+ mContext,
+ () -> mBouncerInteractor,
+ mSceneTestUtils.getSceneContainerFlags()
);
}
@@ -227,8 +235,8 @@
setupLockIconViewMocks();
}
- protected void init(boolean useMigrationFlag) {
- mFeatureFlags.set(DOZING_MIGRATION_1, useMigrationFlag);
+ protected void init(boolean useDozeMigrationFlag) {
+ mFeatureFlags.set(DOZING_MIGRATION_1, useDozeMigrationFlag);
mUnderTest.setLockIconView(mLockIconView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 7b920939..4bacc3d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -404,6 +405,49 @@
// THEN uses perform haptic feedback
verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+ }
+ @Test
+ public void longPress_showBouncer_sceneContainerNotEnabled() {
+ init(/* useMigrationFlag= */ false);
+ mSceneTestUtils.getSceneContainerFlags().setEnabled(false);
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
+
+ // WHEN longPress
+ mUnderTest.onLongPress();
+
+ // THEN show primary bouncer via keyguard view controller, not scene container
+ verify(mKeyguardViewController).showPrimaryBouncer(anyBoolean());
+ verify(mBouncerInteractor, never()).showOrUnlockDevice(any());
+ }
+
+ @Test
+ public void longPress_showBouncer() {
+ init(/* useMigrationFlag= */ false);
+ mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
+
+ // WHEN longPress
+ mUnderTest.onLongPress();
+
+ // THEN show primary bouncer
+ verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean());
+ verify(mBouncerInteractor).showOrUnlockDevice(any());
+ }
+
+ @Test
+ public void longPress_falsingTriggered_doesNotShowBouncer() {
+ init(/* useMigrationFlag= */ false);
+ mSceneTestUtils.getSceneContainerFlags().setEnabled(true);
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
+
+ // WHEN longPress
+ mUnderTest.onLongPress();
+
+ // THEN don't show primary bouncer
+ verify(mBouncerInteractor, never()).showOrUnlockDevice(any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 679dfef..2c80035 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -12,6 +12,7 @@
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
@@ -22,6 +23,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -54,6 +56,7 @@
keyguardBypassController = keyguardBypassController,
keyguardStateController = keyguardStateController,
)
+ testScope.runCurrent()
}
@Test
@@ -66,8 +69,7 @@
assertThat(isUnlocked).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- Mockito.verify(keyguardStateController, Mockito.atLeastOnce())
- .addCallback(captor.capture())
+ verify(keyguardStateController, Mockito.atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
@@ -98,7 +100,12 @@
testScope.runTest {
whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
whenever(keyguardBypassController.bypassEnabled).thenAnswer { false }
- assertThat(underTest.isBypassEnabled()).isFalse()
+ withArgCaptor {
+ verify(keyguardBypassController).registerOnBypassStateChangedListener(capture())
+ }
+ .onBypassStateChanged(false)
+ runCurrent()
+ assertThat(underTest.isBypassEnabled.value).isFalse()
}
@Test
@@ -106,7 +113,12 @@
testScope.runTest {
whenever(keyguardBypassController.isBypassEnabled).thenAnswer { true }
whenever(keyguardBypassController.bypassEnabled).thenAnswer { true }
- assertThat(underTest.isBypassEnabled()).isTrue()
+ withArgCaptor {
+ verify(keyguardBypassController).registerOnBypassStateChangedListener(capture())
+ }
+ .onBypassStateChanged(true)
+ runCurrent()
+ assertThat(underTest.isBypassEnabled.value).isTrue()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index c4cd8a4..c13fde7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -218,14 +218,14 @@
fun isBypassEnabled_enabledInRepository_true() =
testScope.runTest {
utils.deviceEntryRepository.setBypassEnabled(true)
- assertThat(underTest.isBypassEnabled()).isTrue()
+ assertThat(underTest.isBypassEnabled.value).isTrue()
}
@Test
fun isBypassEnabled_disabledInRepository_false() =
testScope.runTest {
utils.deviceEntryRepository.setBypassEnabled(false)
- assertThat(underTest.isBypassEnabled()).isFalse()
+ assertThat(underTest.isBypassEnabled.value).isFalse()
}
private fun switchToScene(sceneKey: SceneKey) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index bbe6823..900413c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -46,6 +47,7 @@
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardKeyEventInteractorTest : SysuiTestCase() {
@@ -132,58 +134,73 @@
}
@Test
- fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
+ fun dispatchKeyEvent_menuActionUp_awakeKeyguard_showsPrimaryBouncer() {
powerInteractor.setAwakeForTest()
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(shadeController).animateCollapseShadeForced()
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU)
}
@Test
- fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() {
+ fun dispatchKeyEvent_menuActionUp_awakeShadeLocked_collapsesShade() {
powerInteractor.setAwakeForTest()
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- // action down: does NOT collapse the shade
- val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
- verify(shadeController, never()).animateCollapseShadeForced()
-
- // action up: collapses the shade
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(shadeController).animateCollapseShadeForced()
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU)
}
@Test
- fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
+ fun dispatchKeyEvent_menuActionUp_asleepKeyguard_neverCollapsesShade() {
powerInteractor.setAsleepForTest()
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
- verify(shadeController, never()).animateCollapseShadeForced()
+ verifyActionsDoNothing(KeyEvent.KEYCODE_MENU)
}
@Test
- fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
+ fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() {
powerInteractor.setAwakeForTest()
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false)
- // action down: does NOT collapse the shade
- val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
- assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
- verify(shadeController, never()).animateCollapseShadeForced()
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE)
+ }
- // action up: collapses the shade
- val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
- assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(shadeController).animateCollapseShadeForced()
+ @Test
+ fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() {
+ powerInteractor.setAwakeForTest()
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() {
+ powerInteractor.setAwakeForTest()
+ whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_awakeKeyguard_primaryBouncerAlreadyShowing() {
+ powerInteractor.setAwakeForTest()
+ whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(true)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ verifyActionsDoNothing(KeyEvent.KEYCODE_ENTER)
+ }
+
+ @Test
+ fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() {
+ powerInteractor.setAwakeForTest()
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+ verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER)
}
@Test
@@ -253,4 +270,42 @@
.isFalse()
verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
}
+
+ private fun verifyActionUpCollapsesTheShade(keycode: Int) {
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(shadeController).animateCollapseShadeForced()
+ }
+
+ private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) {
+ // action down: does NOT collapse the shade
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+ // action up: collapses the shade
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+ verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true))
+ }
+
+ private fun verifyActionsDoNothing(keycode: Int) {
+ // action down: does nothing
+ val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+ // action up: doesNothing
+ val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+ assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+ verify(shadeController, never()).animateCollapseShadeForced()
+ verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
new file mode 100644
index 0000000..2f8f3bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class NotificationsKeyguardViewStateRepositoryTest : SysuiTestCase() {
+
+ private val testComponent: TestComponent =
+ DaggerNotificationsKeyguardViewStateRepositoryTest_TestComponent.factory()
+ .create(test = this)
+
+ @Test
+ fun areNotifsFullyHidden_reflectsWakeUpCoordinator() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(mockWakeUpCoordinator.notificationsFullyHidden).thenReturn(false)
+ val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
+ runCurrent()
+
+ assertThat(notifsFullyHidden).isFalse()
+
+ withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
+ .onFullyHiddenChanged(true)
+ runCurrent()
+
+ assertThat(notifsFullyHidden).isTrue()
+ }
+ }
+
+ @Test
+ fun isPulseExpanding_reflectsWakeUpCoordinator() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(mockWakeUpCoordinator.isPulseExpanding()).thenReturn(false)
+ val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
+ runCurrent()
+
+ assertThat(isPulseExpanding).isFalse()
+
+ withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
+ .onPulseExpansionChanged(true)
+ runCurrent()
+
+ assertThat(isPulseExpanding).isTrue()
+ }
+ }
+
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ ]
+ )
+ interface TestComponent {
+
+ val underTest: NotificationsKeyguardViewStateRepositoryImpl
+
+ val mockWakeUpCoordinator: NotificationWakeUpCoordinator
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ ): TestComponent
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
new file mode 100644
index 0000000..705a5a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.notification.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class NotificationsKeyguardInteractorTest : SysuiTestCase() {
+
+ private val testComponent: TestComponent =
+ DaggerNotificationsKeyguardInteractorTest_TestComponent.factory().create(test = this)
+
+ @Test
+ fun areNotifsFullyHidden_reflectsRepository() =
+ with(testComponent) {
+ testScope.runTest {
+ repository.setNotificationsFullyHidden(false)
+ val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
+ runCurrent()
+
+ assertThat(notifsFullyHidden).isFalse()
+
+ repository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(notifsFullyHidden).isTrue()
+ }
+ }
+
+ @Test
+ fun isPulseExpanding_reflectsRepository() =
+ with(testComponent) {
+ testScope.runTest {
+ repository.setPulseExpanding(false)
+ val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
+ runCurrent()
+
+ assertThat(isPulseExpanding).isFalse()
+
+ repository.setPulseExpanding(true)
+ runCurrent()
+
+ assertThat(isPulseExpanding).isTrue()
+ }
+ }
+
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ ]
+ )
+ interface TestComponent {
+
+ val underTest: NotificationsKeyguardInteractor
+
+ val repository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase): TestComponent
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
index 7caa5ccc..e57986d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
@@ -26,9 +26,7 @@
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.whenever
import dagger.BindsInstance
import dagger.Component
import org.junit.Assert.assertFalse
@@ -37,7 +35,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@@ -46,7 +43,6 @@
class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
@Mock private lateinit var dozeParams: DozeParameters
- @Mock private lateinit var aodIcons: NotificationIconContainer
private lateinit var testComponent: TestComponent
private val underTest
@@ -85,15 +81,6 @@
assertTrue(underTest.shouldShowLowPriorityIcons())
}
- @Test
- fun testAppearResetsTranslation() {
- underTest.setupAodIcons(aodIcons)
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
- underTest.appearAodIcons()
- verify(aodIcons).translationY = 0f
- verify(aodIcons).alpha = 1.0f
- }
-
@SysUISingleton
@Component(
modules =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 99c3b19..31efebb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -37,10 +38,13 @@
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.AnimatedValue
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -59,19 +63,24 @@
class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Mock private lateinit var dozeParams: DozeParameters
+ @Mock private lateinit var screenOffAnimController: ScreenOffAnimationController
private lateinit var testComponent: TestComponent
- private val underTest
+ private val underTest: NotificationIconContainerAlwaysOnDisplayViewModel
get() = testComponent.underTest
- private val deviceProvisioningRepository
+ private val deviceEntryRepository: FakeDeviceEntryRepository
+ get() = testComponent.deviceEntryRepository
+ private val deviceProvisioningRepository: FakeDeviceProvisioningRepository
get() = testComponent.deviceProvisioningRepository
- private val keyguardRepository
+ private val keyguardRepository: FakeKeyguardRepository
get() = testComponent.keyguardRepository
- private val keyguardTransitionRepository
+ private val keyguardTransitionRepository: FakeKeyguardTransitionRepository
get() = testComponent.keyguardTransitionRepository
- private val powerRepository
+ private val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
+ get() = testComponent.notifsKeyguardRepository
+ private val powerRepository: FakePowerRepository
get() = testComponent.powerRepository
- private val scope
+ private val scope: TestScope
get() = testComponent.scope
@Before
@@ -84,12 +93,14 @@
test = this,
featureFlags =
FakeFeatureFlagsClassicModule {
- set(Flags.FACE_AUTH_REFACTOR, value = false)
+ setDefault(Flags.FACE_AUTH_REFACTOR)
set(Flags.FULL_SCREEN_USER_SWITCHER, value = false)
+ setDefault(Flags.NEW_AOD_TRANSITION)
},
mocks =
TestMocksModule(
dozeParameters = dozeParams,
+ screenOffAnimationController = screenOffAnimController,
),
)
@@ -251,6 +262,204 @@
assertThat(animationsEnabled).isFalse()
}
+ @Test
+ fun isDozing_startAodTransition() =
+ scope.runTest {
+ val isDozing by collectLastValue(underTest.isDozing)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+ assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ }
+
+ @Test
+ fun isDozing_startDozeTransition() =
+ scope.runTest {
+ val isDozing by collectLastValue(underTest.isDozing)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+ assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = false))
+ }
+
+ @Test
+ fun isDozing_startDozeToAodTransition() =
+ scope.runTest {
+ val isDozing by collectLastValue(underTest.isDozing)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+ assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ }
+
+ @Test
+ fun isNotDozing_startAodToGoneTransition() =
+ scope.runTest {
+ val isDozing by collectLastValue(underTest.isDozing)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+ assertThat(isDozing).isEqualTo(AnimatedValue(false, isAnimating = true))
+ }
+
+ @Test
+ fun isDozing_stopAnimation() =
+ scope.runTest {
+ val isDozing by collectLastValue(underTest.isDozing)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ runCurrent()
+
+ underTest.completeDozeAnimation()
+ runCurrent()
+
+ assertThat(isDozing?.isAnimating).isEqualTo(false)
+ }
+
+ @Test
+ fun isNotVisible_pulseExpanding() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ notifsKeyguardRepository.setPulseExpanding(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isFalse()
+ }
+
+ @Test
+ fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
+ runCurrent()
+
+ assertThat(isVisible).isEqualTo(AnimatedValue(false, isAnimating = false))
+ }
+
+ @Test
+ fun isVisible_bypassEnabled() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ deviceEntryRepository.setBypassEnabled(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ }
+
+ @Test
+ fun isNotVisible_pulseExpanding_notBypassing() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ notifsKeyguardRepository.setPulseExpanding(true)
+ deviceEntryRepository.setBypassEnabled(false)
+ runCurrent()
+
+ assertThat(isVisible?.value).isEqualTo(false)
+ }
+
+ @Test
+ fun isVisible_notifsFullyHidden_bypassEnabled() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(true)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ }
+
+ @Test
+ fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(false)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ }
+
+ @Test
+ fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(true)
+ whenever(dozeParams.displayNeedsBlanking).thenReturn(true)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ }
+
+ @Test
+ fun isVisible_notifsFullyHidden_bypassDisabled() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(true)
+ whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ }
+
+ @Test
+ fun isVisible_stopAnimation() =
+ scope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ notifsKeyguardRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParams.alwaysOn).thenReturn(true)
+ whenever(dozeParams.displayNeedsBlanking).thenReturn(false)
+ notifsKeyguardRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ underTest.completeVisibilityAnimation()
+ runCurrent()
+
+ assertThat(isVisible?.isAnimating).isEqualTo(false)
+ }
+
@SysUISingleton
@Component(
modules =
@@ -264,9 +473,11 @@
val underTest: NotificationIconContainerAlwaysOnDisplayViewModel
+ val deviceEntryRepository: FakeDeviceEntryRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
val keyguardRepository: FakeKeyguardRepository
val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ val notifsKeyguardRepository: FakeNotificationsKeyguardViewStateRepository
val powerRepository: FakePowerRepository
val scope: TestScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index d1518f7..e1e7f92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -82,6 +82,7 @@
DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
.create(
test = this,
+ // Configurable bindings
featureFlags =
FakeFeatureFlagsClassicModule {
set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -248,6 +249,7 @@
modules =
[
SysUITestModule::class,
+ // Real impls
BiometricsDomainLayerModule::class,
UserDomainLayerModule::class,
]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index c9b77c5..9c20e54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -54,9 +54,9 @@
import android.widget.TextView;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -132,7 +132,8 @@
public void testBindNotification_SetsTextApplicationName() {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(),
- mMockNotificationRow, mAssistantFeedbackController);
+ mMockNotificationRow, mAssistantFeedbackController, mStatusBarService,
+ mNotificationGutsManager);
final TextView textView = mFeedbackInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
}
@@ -143,7 +144,8 @@
when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class)))
.thenReturn(iconDrawable);
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(),
- mMockNotificationRow, mAssistantFeedbackController);
+ mMockNotificationRow, mAssistantFeedbackController, mStatusBarService,
+ mNotificationGutsManager);
final ImageView iconView = mFeedbackInfo.findViewById(R.id.pkg_icon);
assertEquals(iconDrawable, iconView.getDrawable());
}
@@ -153,7 +155,7 @@
when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class)))
.thenReturn(STATUS_SILENCED);
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow,
- mAssistantFeedbackController);
+ mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically demoted to Silent by the system. "
+ "Let the developer know your feedback. Was this correct?",
@@ -165,7 +167,7 @@
when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class)))
.thenReturn(STATUS_PROMOTED);
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow,
- mAssistantFeedbackController);
+ mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically ranked higher in your shade. "
+ "Let the developer know your feedback. Was this correct?",
@@ -177,7 +179,7 @@
when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class)))
.thenReturn(STATUS_ALERTED);
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow,
- mAssistantFeedbackController);
+ mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically promoted to Default by the system. "
+ "Let the developer know your feedback. Was this correct?",
@@ -189,7 +191,7 @@
when(mAssistantFeedbackController.getFeedbackStatus(any(NotificationEntry.class)))
.thenReturn(STATUS_DEMOTED);
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow,
- mAssistantFeedbackController);
+ mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager);
TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
assertEquals("This notification was automatically ranked lower in your shade. "
+ "Let the developer know your feedback. Was this correct?",
@@ -199,7 +201,7 @@
@Test
public void testPositiveFeedback() {
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow,
- mAssistantFeedbackController);
+ mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager);
final View yes = mFeedbackInfo.findViewById(R.id.yes);
yes.performClick();
@@ -216,7 +218,7 @@
.thenReturn(true);
mFeedbackInfo.bindGuts(mMockPackageManager, mSbn, getEntry(), mMockNotificationRow,
- mAssistantFeedbackController);
+ mAssistantFeedbackController, mStatusBarService, mNotificationGutsManager);
final View no = mFeedbackInfo.findViewById(R.id.no);
no.performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 1ab36b8..8a730cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -195,6 +195,7 @@
mWindowRootViewVisibilityInteractor,
mNotificationLockscreenUserManager,
mStatusBarStateController,
+ mBarService,
mDeviceProvisionedController,
mMetricsLogger,
mHeadsUpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
new file mode 100644
index 0000000..aaf8d07
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AnimatedValueTest : SysuiTestCase() {
+
+ @Test
+ fun animatableEvent_updatesValue() = runTest {
+ val events = MutableSharedFlow<AnimatableEvent<Int>>()
+ val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val value by collectLastValue(values)
+ runCurrent()
+
+ events.emit(AnimatableEvent(value = 1, startAnimating = false))
+
+ assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ }
+
+ @Test
+ fun animatableEvent_startAnimation() = runTest {
+ val events = MutableSharedFlow<AnimatableEvent<Int>>()
+ val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val value by collectLastValue(values)
+ runCurrent()
+
+ events.emit(AnimatableEvent(value = 1, startAnimating = true))
+
+ assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = true))
+ }
+
+ @Test
+ fun animatableEvent_startAnimation_alreadyAnimating() = runTest {
+ val events = MutableSharedFlow<AnimatableEvent<Int>>()
+ val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val value by collectLastValue(values)
+ runCurrent()
+
+ events.emit(AnimatableEvent(value = 1, startAnimating = true))
+ events.emit(AnimatableEvent(value = 2, startAnimating = true))
+
+ assertThat(value).isEqualTo(AnimatedValue(value = 2, isAnimating = true))
+ }
+
+ @Test
+ fun animatedValue_stopAnimating() = runTest {
+ val events = MutableSharedFlow<AnimatableEvent<Int>>()
+ val stopEvent = MutableSharedFlow<Unit>()
+ val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+ val value by collectLastValue(values)
+ runCurrent()
+
+ events.emit(AnimatableEvent(value = 1, startAnimating = true))
+ stopEvent.emit(Unit)
+
+ assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ }
+
+ @Test
+ fun animatedValue_stopAnimating_notAnimating() = runTest {
+ val events = MutableSharedFlow<AnimatableEvent<Int>>()
+ val stopEvent = MutableSharedFlow<Unit>()
+ val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+ values.launchIn(backgroundScope)
+ runCurrent()
+
+ events.emit(AnimatableEvent(value = 1, startAnimating = false))
+
+ assertThat(stopEvent.subscriptionCount.value).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 813197b1..dc5fd95 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
import com.android.systemui.statusbar.policy.FakeSplitShadeStateControllerModule
import com.android.systemui.util.concurrency.FakeExecutorModule
+import com.android.systemui.util.time.FakeSystemClockModule
import dagger.Module
@Module(
@@ -36,6 +37,7 @@
FakeSceneModule::class,
FakeSettingsModule::class,
FakeSplitShadeStateControllerModule::class,
+ FakeSystemClockModule::class,
FakeSystemUiDataLayerModule::class,
FakeUiEventLoggerModule::class,
]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/FakeAuthenticationDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/FakeAuthenticationDataLayerModule.kt
new file mode 100644
index 0000000..8fa6695
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/FakeAuthenticationDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.authentication.data
+
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeAuthenticationRepositoryModule::class])
+object FakeAuthenticationDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 4fc3e3f..ddfe79a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -24,10 +24,17 @@
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
class FakeAuthenticationRepository(
private val deviceEntryRepository: FakeDeviceEntryRepository,
@@ -201,3 +208,19 @@
}
}
}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@Module(includes = [FakeAuthenticationRepositoryModule.Bindings::class])
+object FakeAuthenticationRepositoryModule {
+ @Provides
+ @SysUISingleton
+ fun provideFake(
+ deviceEntryRepository: FakeDeviceEntryRepository,
+ scope: TestScope,
+ ) = FakeAuthenticationRepository(deviceEntryRepository, currentTime = { scope.currentTime })
+
+ @Module
+ interface Bindings {
+ @Binds fun bindFake(fake: FakeAuthenticationRepository): AuthenticationRepository
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
index 29fb52a..cffbf02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/data/FakeSystemUiDataLayerModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.data
+import com.android.systemui.authentication.data.FakeAuthenticationDataLayerModule
import com.android.systemui.bouncer.data.repository.FakeBouncerDataLayerModule
import com.android.systemui.common.ui.data.FakeCommonDataLayerModule
import com.android.systemui.deviceentry.data.FakeDeviceEntryDataLayerModule
@@ -29,6 +30,7 @@
@Module(
includes =
[
+ FakeAuthenticationDataLayerModule::class,
FakeBouncerDataLayerModule::class,
FakeCommonDataLayerModule::class,
FakeDeviceEntryDataLayerModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryDataLayerModule.kt
new file mode 100644
index 0000000..f4feee1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryDataLayerModule.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.data.repository
+
+import dagger.Module
+
+@Module(includes = [FakeDeviceEntryRepositoryModule::class]) object FakeDeviceEntryDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 26d95c0..f029348 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2023 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.deviceentry.data.repository
import com.android.systemui.dagger.SysUISingleton
@@ -13,15 +28,13 @@
class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
private var isInsecureLockscreenEnabled = true
- private var isBypassEnabled = false
+
+ private val _isBypassEnabled = MutableStateFlow(false)
+ override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
private val _isUnlocked = MutableStateFlow(false)
override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
- override fun isBypassEnabled(): Boolean {
- return isBypassEnabled
- }
-
override suspend fun isInsecureLockscreenEnabled(): Boolean {
return isInsecureLockscreenEnabled
}
@@ -35,7 +48,7 @@
}
fun setBypassEnabled(isBypassEnabled: Boolean) {
- this.isBypassEnabled = isBypassEnabled
+ _isBypassEnabled.value = isBypassEnabled
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
index 822edfc7..e59f642 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.data
import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule
+import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule
import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule
import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule
import dagger.Module
@@ -24,6 +25,7 @@
includes =
[
FakeStatusBarDisableFlagsDataLayerModule::class,
+ FakeStatusBarNotificationsDataLayerModule::class,
FakeStatusBarPipelineDataLayerModule::class,
FakeStatusBarPolicyDataLayerModule::class,
]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
new file mode 100644
index 0000000..788e3aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.notification.data
+
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardStateRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeNotificationsKeyguardStateRepositoryModule::class])
+object FakeStatusBarNotificationsDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
new file mode 100644
index 0000000..5d3cb4d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeNotificationsKeyguardViewStateRepository @Inject constructor() :
+ NotificationsKeyguardViewStateRepository {
+ private val _notificationsFullyHidden = MutableStateFlow(false)
+ override val areNotificationsFullyHidden: Flow<Boolean> = _notificationsFullyHidden
+
+ private val _isPulseExpanding = MutableStateFlow(false)
+ override val isPulseExpanding: Flow<Boolean> = _isPulseExpanding
+
+ fun setNotificationsFullyHidden(fullyHidden: Boolean) {
+ _notificationsFullyHidden.value = fullyHidden
+ }
+
+ fun setPulseExpanding(expanding: Boolean) {
+ _isPulseExpanding.value = expanding
+ }
+}
+
+@Module
+interface FakeNotificationsKeyguardStateRepositoryModule {
+ @Binds
+ fun bindFake(
+ fake: FakeNotificationsKeyguardViewStateRepository
+ ): NotificationsKeyguardViewStateRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
index 5de05c2..1f48d94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutorModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.util.concurrency
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.time.FakeSystemClock
import dagger.Binds
@@ -22,14 +23,12 @@
import dagger.Provides
import java.util.concurrent.Executor
-@Module(includes = [FakeExecutorModule.Bindings::class])
-class FakeExecutorModule(
- @get:Provides val clock: FakeSystemClock = FakeSystemClock(),
-) {
- @get:Provides val executor = FakeExecutor(clock)
+@Module
+interface FakeExecutorModule {
+ @Binds @Main @SysUISingleton fun bindMainExecutor(executor: FakeExecutor): Executor
- @Module
- interface Bindings {
- @Binds @Main fun bindMainExecutor(executor: FakeExecutor): Executor
+ companion object {
+ @Provides
+ fun provideFake(clock: FakeSystemClock) = FakeExecutor(clock)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockModule.kt
new file mode 100644
index 0000000..3e3d7cb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.time
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface FakeSystemClockModule {
+ @Binds fun bindFake(fake: FakeSystemClock): SystemClock
+
+ companion object {
+ @Provides @SysUISingleton fun providesFake() = FakeSystemClock()
+ }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 7e09b5e..258820a 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -24,6 +24,7 @@
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1660,8 +1661,21 @@
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
+ final String pkg = componentName.getPackageName();
+ final ProviderId id;
+ if (!mPackageManagerInternal.isSameApp(pkg, callingUid, userId)) {
+ // If the calling process is requesting to pin appwidgets from another process,
+ // check if the calling process has the necessary permission.
+ if (!injectHasAccessWidgetsPermission(Binder.getCallingPid(), callingUid)) {
+ return false;
+ }
+ id = new ProviderId(mPackageManagerInternal.getPackageUid(
+ pkg, 0 /* flags */, userId), componentName);
+ } else {
+ id = new ProviderId(callingUid, componentName);
+ }
// Look for the widget associated with the caller.
- Provider provider = lookupProviderLocked(new ProviderId(callingUid, componentName));
+ Provider provider = lookupProviderLocked(id);
if (provider == null || provider.zombie) {
return false;
}
@@ -1675,6 +1689,14 @@
.requestPinAppWidget(callingPackage, info, extras, resultSender, userId);
}
+ /**
+ * Returns true if the caller has the proper permission to access app widgets.
+ */
+ private boolean injectHasAccessWidgetsPermission(int callingPid, int callingUid) {
+ return mContext.checkPermission(Manifest.permission.CLEAR_APP_USER_DATA,
+ callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
int profileId, String packageName) {
@@ -4131,7 +4153,7 @@
return false;
}
- @GuardedBy("mLock")
+ @GuardedBy("AppWidgetServiceImpl.mLock")
public AppWidgetProviderInfo getInfoLocked(Context context) {
if (!mInfoParsed) {
// parse
@@ -4159,18 +4181,18 @@
* be completely parsed and only contain placeHolder information like
* {@link AppWidgetProviderInfo#providerInfo}
*/
- @GuardedBy("mLock")
+ @GuardedBy("AppWidgetServiceImpl.mLock")
public AppWidgetProviderInfo getPartialInfoLocked() {
return info;
}
- @GuardedBy("mLock")
+ @GuardedBy("AppWidgetServiceImpl.mLock")
public void setPartialInfoLocked(AppWidgetProviderInfo info) {
this.info = info;
mInfoParsed = false;
}
- @GuardedBy("mLock")
+ @GuardedBy("AppWidgetServiceImpl.mLock")
public void setInfoLocked(AppWidgetProviderInfo info) {
this.info = info;
mInfoParsed = true;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0220dec..265ed46 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2437,7 +2437,7 @@
+ id + " destroyed");
return;
}
- fillInIntent = createAuthFillInIntentLocked(requestId, extras);
+ fillInIntent = createAuthFillInIntentLocked(requestId, extras, /* authExtras= */ null);
if (fillInIntent == null) {
forceRemoveFromServiceLocked();
return;
@@ -5558,7 +5558,8 @@
mPresentationStatsEventLogger.maybeSetAuthenticationType(
AUTHENTICATION_TYPE_DATASET_AUTHENTICATION);
setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
- final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
+ final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState,
+ dataset.getAuthenticationExtras());
if (fillInIntent == null) {
forceRemoveFromServiceLocked();
return;
@@ -5574,7 +5575,8 @@
// TODO: this should never be null, but we got at least one occurrence, probably due to a race.
@GuardedBy("mLock")
@Nullable
- private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
+ private Intent createAuthFillInIntentLocked(int requestId, Bundle extras,
+ @Nullable Bundle authExtras) {
final Intent fillInIntent = new Intent();
final FillContext context = getFillContextByRequestIdLocked(requestId);
@@ -5591,6 +5593,9 @@
}
fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
+ if (authExtras != null) {
+ fillInIntent.putExtra(AutofillManager.EXTRA_AUTH_STATE, authExtras);
+ }
return fillInIntent;
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 330742a..5fb889a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5023,7 +5023,7 @@
p.setDataPosition(0);
Bundle simulateBundle = p.readBundle();
p.recycle();
- Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
+ Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
if (intent != null && intent.getClass() != Intent.class) {
return false;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index d8a2695..3771c05 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1255,11 +1255,10 @@
killProcessGroup = true;
}
if (killProcessGroup) {
- if (async) {
- ProcessList.killProcessGroup(uid, mPid);
- } else {
+ if (!async) {
Process.sendSignalToProcessGroup(uid, mPid, OsConstants.SIGKILL);
}
+ ProcessList.killProcessGroup(uid, mPid);
}
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 816043e..1ba1f55 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -137,9 +137,11 @@
"core_graphics",
"haptics",
"hardware_backed_security_mainline",
+ "input",
"machine_learning",
"mainline_sdk",
"media_audio",
+ "media_drm",
"media_solutions",
"nfc",
"pixel_audio_android",
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index f74b45c..e42b664 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -875,6 +875,10 @@
public void simulateVhalFingerDown(int userId, int sensorId) {
Slog.d(getTag(), "Simulate virtual HAL finger down event");
final AidlSession session = mFingerprintSensors.get(sensorId).getSessionForUser(userId);
+ if (session == null) {
+ Slog.e(getTag(), "no existing hal session found - aborting");
+ return;
+ }
final PointerContext pc = new PointerContext();
try {
session.getSession().onPointerDownWithContext(pc);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 652e6cf..39f0b13 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -105,16 +105,21 @@
public void resetHdrConfig(HdrBrightnessData data, int width, int height,
float minimumHdrPercentOfScreen, IBinder displayToken) {
mHdrBrightnessData = data;
- mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
+ mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
+ : (float) (width * height) * minimumHdrPercentOfScreen;
if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
mHdrListener.unregister(mRegisteredDisplayToken);
mHdrVisible = false;
+ mRegisteredDisplayToken = null;
}
- if (displayToken != null) { // new token not null, subscribe
+ // new token not null and hdr min % of the screen is set, subscribe.
+ // e.g. for virtual display, HBM data will be missing and HdrListener
+ // should not be registered
+ if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
mHdrListener.register(displayToken);
+ mRegisteredDisplayToken = displayToken;
}
- mRegisteredDisplayToken = displayToken;
}
recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index d5382cb..e66fa5b 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -138,19 +138,20 @@
}
private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
- // TODO(b/299462337) Remove when the infrastructure is ready.
- if ((Build.IS_ENG || Build.IS_USERDEBUG)
- && SystemProperties.getBoolean("persist.sys." + flagName, false)) {
- return true;
- }
+ boolean flagValue = false;
try {
- return flagFunction.get();
+ flagValue = flagFunction.get();
} catch (Throwable ex) {
if (DEBUG) {
Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
}
- return false;
}
+ // TODO(b/299462337) Remove when the infrastructure is ready.
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ return SystemProperties.getBoolean("persist.sys." + flagName + "-override",
+ flagValue);
+ }
+ return flagValue;
}
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 0eb620f..bad6bf0 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -71,6 +71,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
@@ -99,7 +100,7 @@
*
* @hide
*/
-final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
+class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private static final String TAG = "KeyboardLayoutManager";
@@ -1295,7 +1296,8 @@
}
@SuppressLint("MissingPermission")
- private List<ImeInfo> getImeInfoListForLayoutMapping() {
+ @VisibleForTesting
+ public List<ImeInfo> getImeInfoListForLayoutMapping() {
List<ImeInfo> imeInfoList = new ArrayList<>();
UserManager userManager = Objects.requireNonNull(
mContext.getSystemService(UserManager.class));
@@ -1402,7 +1404,8 @@
}
}
- private static class ImeInfo {
+ @VisibleForTesting
+ public static class ImeInfo {
@UserIdInt int mUserId;
@NonNull InputMethodSubtypeHandle mImeSubtypeHandle;
@Nullable InputMethodSubtype mImeSubtype;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 097656c..3a6664a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -333,6 +333,11 @@
static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP = 0;
static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME = 1;
+ // must match: config_shortPressOnSettingsBehavior in config.xml
+ static final int SHORT_PRESS_SETTINGS_NOTHING = 0;
+ static final int SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL = 1;
+ static final int LAST_SHORT_PRESS_SETTINGS_BEHAVIOR = SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL;
+
static final int PENDING_KEY_NULL = -1;
// Must match: config_shortPressOnStemPrimaryBehavior in config.xml
@@ -611,6 +616,9 @@
// What we do when the user double-taps on home
int mDoubleTapOnHomeBehavior;
+ // What we do when the user presses on settings
+ int mShortPressOnSettingsBehavior;
+
// Must match config_primaryShortPressTargetActivity in config.xml
ComponentName mPrimaryShortPressTargetActivity;
@@ -2766,6 +2774,13 @@
if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
}
+
+ mShortPressOnSettingsBehavior = res.getInteger(
+ com.android.internal.R.integer.config_shortPressOnSettingsBehavior);
+ if (mShortPressOnSettingsBehavior < SHORT_PRESS_SETTINGS_NOTHING
+ || mShortPressOnSettingsBehavior > LAST_SHORT_PRESS_SETTINGS_BEHAVIOR) {
+ mShortPressOnSettingsBehavior = SHORT_PRESS_SETTINGS_NOTHING;
+ }
}
private void updateSettings() {
@@ -3632,6 +3647,15 @@
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
return true;
+ case KeyEvent.KEYCODE_SETTINGS:
+ if (mShortPressOnSettingsBehavior == SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL) {
+ if (!down) {
+ toggleNotificationPanel();
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+ }
+ return true;
+ }
+ break;
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -6272,6 +6296,9 @@
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mShortPressOnSettingsBehavior=");
+ pw.println(shortPressOnSettingsBehaviorToString(mShortPressOnSettingsBehavior));
+ pw.print(prefix);
pw.print("mLongPressOnPowerAssistantTimeoutMs=");
pw.println(mLongPressOnPowerAssistantTimeoutMs);
pw.print(prefix);
@@ -6470,6 +6497,17 @@
}
}
+ private static String shortPressOnSettingsBehaviorToString(int behavior) {
+ switch (behavior) {
+ case SHORT_PRESS_SETTINGS_NOTHING:
+ return "SHORT_PRESS_SETTINGS_NOTHING";
+ case SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL:
+ return "SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
private static String veryLongPressOnPowerBehaviorToString(int behavior) {
switch (behavior) {
case VERY_LONG_PRESS_POWER_NOTHING:
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index 2439159..73bcc8d 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -66,29 +66,29 @@
public final class CompatModePackages {
/**
- * {@link CompatModePackages#DOWNSCALED_INVERSE} is the gatekeeper of all per-app buffer inverse
- * downscale changes. Enabling this change will allow the following scaling factors:
- * {@link CompatModePackages#DOWNSCALE_90}
- * {@link CompatModePackages#DOWNSCALE_85}
- * {@link CompatModePackages#DOWNSCALE_80}
- * {@link CompatModePackages#DOWNSCALE_75}
- * {@link CompatModePackages#DOWNSCALE_70}
- * {@link CompatModePackages#DOWNSCALE_65}
- * {@link CompatModePackages#DOWNSCALE_60}
- * {@link CompatModePackages#DOWNSCALE_55}
- * {@link CompatModePackages#DOWNSCALE_50}
- * {@link CompatModePackages#DOWNSCALE_45}
- * {@link CompatModePackages#DOWNSCALE_40}
- * {@link CompatModePackages#DOWNSCALE_35}
- * {@link CompatModePackages#DOWNSCALE_30}
+ * <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> is the gatekeeper of all per-app buffer
+ * inverse downscale changes. Enabling this change will allow the following scaling factors:
+ * <a href="#DOWNSCALE_90">DOWNSCALE_90</a>
+ * <a href="#DOWNSCALE_85">DOWNSCALE_85</a>
+ * <a href="#DOWNSCALE_80">DOWNSCALE_80</a>
+ * <a href="#DOWNSCALE_75">DOWNSCALE_75</a>
+ * <a href="#DOWNSCALE_70">DOWNSCALE_70</a>
+ * <a href="#DOWNSCALE_65">DOWNSCALE_65</a>
+ * <a href="#DOWNSCALE_60">DOWNSCALE_60</a>
+ * <a href="#DOWNSCALE_55">DOWNSCALE_55</a>
+ * <a href="#DOWNSCALE_50">DOWNSCALE_50</a>
+ * <a href="#DOWNSCALE_45">DOWNSCALE_45</a>
+ * <a href="#DOWNSCALE_40">DOWNSCALE_40</a>
+ * <a href="#DOWNSCALE_35">DOWNSCALE_35</a>
+ * <a href="#DOWNSCALE_30">DOWNSCALE_30</a>
*
- * If {@link CompatModePackages#DOWNSCALED_INVERSE} is enabled for an app package, then the app
- * will be forcibly resized to the lowest enabled scaling factor e.g. 1/0.8 if both 1/0.8 and
- * 1/0.7 (* 100%) were enabled.
+ * If <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> is enabled for an app package, then
+ * the app will be forcibly resized to the lowest enabled scaling factor e.g. 1/0.8 if both
+ * 1/0.8 and 1/0.7 (* 100%) were enabled.
*
- * When both {@link CompatModePackages#DOWNSCALED_INVERSE}
- * and {@link CompatModePackages#DOWNSCALED} are enabled, then
- * {@link CompatModePackages#DOWNSCALED_INVERSE} takes precedence.
+ * When both <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a>
+ * and <a href="#DOWNSCALED">DOWNSCALED</a> are enabled, then
+ * <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> takes precedence.
*/
@ChangeId
@Disabled
@@ -96,29 +96,29 @@
public static final long DOWNSCALED_INVERSE = 273564678L; // This is a Bug ID.
/**
- * {@link CompatModePackages#DOWNSCALED} is the gatekeeper of all per-app buffer downscaling
+ * <a href="#DOWNSCALED">DOWNSCALED</a> is the gatekeeper of all per-app buffer downscaling
* changes. Enabling this change will allow the following scaling factors:
- * {@link CompatModePackages#DOWNSCALE_90}
- * {@link CompatModePackages#DOWNSCALE_85}
- * {@link CompatModePackages#DOWNSCALE_80}
- * {@link CompatModePackages#DOWNSCALE_75}
- * {@link CompatModePackages#DOWNSCALE_70}
- * {@link CompatModePackages#DOWNSCALE_65}
- * {@link CompatModePackages#DOWNSCALE_60}
- * {@link CompatModePackages#DOWNSCALE_55}
- * {@link CompatModePackages#DOWNSCALE_50}
- * {@link CompatModePackages#DOWNSCALE_45}
- * {@link CompatModePackages#DOWNSCALE_40}
- * {@link CompatModePackages#DOWNSCALE_35}
- * {@link CompatModePackages#DOWNSCALE_30}
+ * <a href="#DOWNSCALE_90">DOWNSCALE_90</a>
+ * <a href="#DOWNSCALE_85">DOWNSCALE_85</a>
+ * <a href="#DOWNSCALE_80">DOWNSCALE_80</a>
+ * <a href="#DOWNSCALE_75">DOWNSCALE_75</a>
+ * <a href="#DOWNSCALE_70">DOWNSCALE_70</a>
+ * <a href="#DOWNSCALE_65">DOWNSCALE_65</a>
+ * <a href="#DOWNSCALE_60">DOWNSCALE_60</a>
+ * <a href="#DOWNSCALE_55">DOWNSCALE_55</a>
+ * <a href="#DOWNSCALE_50">DOWNSCALE_50</a>
+ * <a href="#DOWNSCALE_45">DOWNSCALE_45</a>
+ * <a href="#DOWNSCALE_40">DOWNSCALE_40</a>
+ * <a href="#DOWNSCALE_35">DOWNSCALE_35</a>
+ * <a href="#DOWNSCALE_30">DOWNSCALE_30</a>
*
- * If {@link CompatModePackages#DOWNSCALED} is enabled for an app package, then the app will be
+ * If <a href="#DOWNSCALED">DOWNSCALED</a> is enabled for an app package, then the app will be
* forcibly resized to the highest enabled scaling factor e.g. 80% if both 80% and 70% were
* enabled.
*
- * When both {@link CompatModePackages#DOWNSCALED_INVERSE}
- * and {@link CompatModePackages#DOWNSCALED} are enabled, then
- * {@link CompatModePackages#DOWNSCALED_INVERSE} takes precedence.
+ * When both <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a>
+ * and <a href="#DOWNSCALED">DOWNSCALED</a> are enabled, then
+ * <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> takes precedence.
*/
@ChangeId
@Disabled
@@ -126,12 +126,13 @@
public static final long DOWNSCALED = 168419799L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_90} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_90">DOWNSCALE_90</a> for a package will force the app to assume it's
* running on a display with 90% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 111.11% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 111.11% the vertical and horizontal resolution of
+ * the real display
*/
@ChangeId
@Disabled
@@ -139,12 +140,13 @@
public static final long DOWNSCALE_90 = 182811243L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_85} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_85">DOWNSCALE_85</a> for a package will force the app to assume it's
* running on a display with 85% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 117.65% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 117.65% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -152,12 +154,13 @@
public static final long DOWNSCALE_85 = 189969734L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_80} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_80">DOWNSCALE_80</a> for a package will force the app to assume it's
* running on a display with 80% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 125% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 125% the vertical and horizontal resolution of the real
+ * display
*/
@ChangeId
@Disabled
@@ -165,12 +168,13 @@
public static final long DOWNSCALE_80 = 176926753L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_75} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_75">DOWNSCALE_75</a> for a package will force the app to assume it's
* running on a display with 75% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 133.33% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 133.33% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -178,12 +182,13 @@
public static final long DOWNSCALE_75 = 189969779L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_70} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_70">DOWNSCALE_70</a> for a package will force the app to assume it's
* running on a display with 70% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 142.86% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 142.86% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -191,12 +196,13 @@
public static final long DOWNSCALE_70 = 176926829L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_65} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_65">DOWNSCALE_65</a> for a package will force the app to assume it's
* running on a display with 65% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 153.85% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 153.85% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -204,12 +210,13 @@
public static final long DOWNSCALE_65 = 189969744L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_60} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_60">DOWNSCALE_60</a> for a package will force the app to assume it's
* running on a display with 60% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 166.67% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 166.67% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -217,12 +224,13 @@
public static final long DOWNSCALE_60 = 176926771L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_55} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_55">DOWNSCALE_55</a> for a package will force the app to assume it's
* running on a display with 55% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 181.82% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 181.82% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -230,12 +238,13 @@
public static final long DOWNSCALE_55 = 189970036L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_50} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_50">DOWNSCALE_50</a> for a package will force the app to assume it's
* running on a display with 50% vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 200% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 200% the vertical and horizontal resolution of the real
+ * display
*/
@ChangeId
@Disabled
@@ -243,12 +252,13 @@
public static final long DOWNSCALE_50 = 176926741L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_45} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_45">DOWNSCALE_45</a> for a package will force the app to assume it's
* running on a display with 45% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 222.22% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 222.22% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -256,12 +266,13 @@
public static final long DOWNSCALE_45 = 189969782L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_40} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_40">DOWNSCALE_40</a> for a package will force the app to assume it's
* running on a display with 40% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 250% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 250% the vertical and horizontal resolution of the real
+ * display
*/
@ChangeId
@Disabled
@@ -269,12 +280,13 @@
public static final long DOWNSCALE_40 = 189970038L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_35} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_35">DOWNSCALE_35</a> for a package will force the app to assume it's
* running on a display with 35% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 285.71% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 285.71% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
@@ -282,12 +294,13 @@
public static final long DOWNSCALE_35 = 189969749L;
/**
- * With {@link CompatModePackages#DOWNSCALED} enabled, subsequently enabling change-id
- * {@link CompatModePackages#DOWNSCALE_30} for a package will force the app to assume it's
+ * With <a href="#DOWNSCALED">DOWNSCALED</a> enabled, subsequently enabling change-id
+ * <a href="#DOWNSCALE_30">DOWNSCALE_30</a> for a package will force the app to assume it's
* running on a display with 30% the vertical and horizontal resolution of the real display.
*
- * With {@link CompatModePackages#DOWNSCALED_INVERSE} enabled will force the app to assume it's
- * running on a display with 333.33% the vertical and horizontal resolution of the real display
+ * With <a href="#DOWNSCALED_INVERSE">DOWNSCALED_INVERSE</a> enabled will force the app to
+ * assume it's running on a display with 333.33% the vertical and horizontal resolution of the
+ * real display
*/
@ChangeId
@Disabled
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f6fe9b1..b7b5c2af 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -218,6 +218,7 @@
import android.view.DisplayInfo;
import android.view.DisplayShape;
import android.view.Gravity;
+import android.view.IDecorViewGestureListener;
import android.view.IDisplayWindowInsetsController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindow;
@@ -471,6 +472,8 @@
private final RemoteCallbackList<ISystemGestureExclusionListener>
mSystemGestureExclusionListeners = new RemoteCallbackList<>();
+ private final RemoteCallbackList<IDecorViewGestureListener> mDecorViewGestureListener =
+ new RemoteCallbackList<>();
private final Region mSystemGestureExclusion = new Region();
private boolean mSystemGestureExclusionWasRestricted = false;
private final Region mSystemGestureExclusionUnrestricted = new Region();
@@ -5968,6 +5971,27 @@
mSystemGestureExclusionListeners.unregister(listener);
}
+ void registerDecorViewGestureListener(IDecorViewGestureListener listener) {
+ mDecorViewGestureListener.register(listener);
+ }
+
+ void unregisterDecorViewGestureListener(IDecorViewGestureListener listener) {
+ mDecorViewGestureListener.unregister(listener);
+ }
+
+ void updateDecorViewGestureIntercepted(IBinder token, boolean intercepted) {
+ for (int i = mDecorViewGestureListener.beginBroadcast() - 1; i >= 0; --i) {
+ try {
+ mDecorViewGestureListener
+ .getBroadcastItem(i)
+ .onInterceptionChanged(token, intercepted);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify DecorViewGestureListener", e);
+ }
+ }
+ mDecorViewGestureListener.finishBroadcast();
+ }
+
void updateKeepClearAreas() {
final Set<Rect> restrictedKeepClearAreas = new ArraySet<>();
final Set<Rect> unrestrictedKeepClearAreas = new ArraySet<>();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e6d4866..3775ccd 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -560,6 +560,16 @@
}
@Override
+ public void reportDecorViewGestureInterceptionChanged(IWindow window, boolean intercepted) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mService.reportDecorViewGestureChanged(this, window, intercepted);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void reportKeepClearAreasChanged(IWindow window, List<Rect> restricted,
List<Rect> unrestricted) {
if (!mSetsUnrestrictedKeepClearAreas && !unrestricted.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 074b404..c9107e8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -249,6 +249,7 @@
import android.view.Gravity;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.ICrossWindowBlurEnabledListener;
+import android.view.IDecorViewGestureListener;
import android.view.IDisplayChangeWindowController;
import android.view.IDisplayFoldListener;
import android.view.IDisplayWindowInsetsController;
@@ -4629,8 +4630,9 @@
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
- throw new IllegalArgumentException("Trying to register visibility event "
- + "for invalid display: " + displayId);
+ throw new IllegalArgumentException(
+ "Trying to register system gesture exclusion event for invalid display: "
+ + displayId);
}
displayContent.registerSystemGestureExclusionListener(listener);
}
@@ -4642,13 +4644,64 @@
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
- throw new IllegalArgumentException("Trying to register visibility event "
- + "for invalid display: " + displayId);
+ throw new IllegalArgumentException(
+ "Trying to unregister system gesture exclusion event for invalid display: "
+ + displayId);
}
displayContent.unregisterSystemGestureExclusionListener(listener);
}
}
+ @Override
+ public void registerDecorViewGestureListener(
+ IDecorViewGestureListener listener, int displayId) {
+ if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
+ "registerDecorViewGestureListener()")) {
+ throw new SecurityException("Requires MONITOR_INPUT permission");
+ }
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ throw new IllegalArgumentException(
+ "Trying to register DecorView gesture event listener"
+ + "for invalid display: "
+ + displayId);
+ }
+ displayContent.registerDecorViewGestureListener(listener);
+ }
+ }
+
+ @Override
+ public void unregisterDecorViewGestureListener(
+ IDecorViewGestureListener listener, int displayId) {
+ if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
+ "unregisterSystemGestureExclusionListener()")) {
+ throw new SecurityException("Requires MONITOR_INPUT permission");
+ }
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ throw new IllegalArgumentException(
+ "Trying to unregister DecorView gesture event listener"
+ + "for invalid display: "
+ + displayId);
+ }
+ displayContent.unregisterDecorViewGestureListener(listener);
+ }
+ }
+
+ void reportDecorViewGestureChanged(Session session, IWindow window, boolean intercepted) {
+ synchronized (mGlobalLock) {
+ final WindowState win =
+ windowForClientLocked(session, window, false /* throwOnError */);
+ if (win == null) {
+ return;
+ }
+ win.getDisplayContent()
+ .updateDecorViewGestureIntercepted(win.mToken.token, intercepted);
+ }
+ }
+
void reportSystemGestureExclusionChanged(Session session, IWindow window,
List<Rect> exclusionRects) {
synchronized (mGlobalLock) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index a4adf58..08df651 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -65,6 +65,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
import com.android.server.infra.AbstractMasterSystemService;
@@ -89,7 +90,7 @@
*/
public final class CredentialManagerService
extends AbstractMasterSystemService<
- CredentialManagerService, CredentialManagerServiceImpl> {
+ CredentialManagerService, CredentialManagerServiceImpl> {
private static final String TAG = "CredManSysService";
private static final String PERMISSION_DENIED_ERROR = "permission_denied";
@@ -110,8 +111,7 @@
/** Cache of all ongoing request sessions per user id. */
@GuardedBy("mLock")
- private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions =
- new SparseArray<>();
+ private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = new SparseArray<>();
private final SessionManager mSessionManager = new SessionManager();
@@ -123,6 +123,8 @@
null,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
mContext = context;
+
+ mPackageMonitor.register(context, context.getMainLooper(), false);
}
@NonNull
@@ -139,8 +141,7 @@
serviceInfos.forEach(
info -> {
services.add(
- new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
- info));
+ new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
});
return services;
}
@@ -216,8 +217,8 @@
for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) {
removeServiceFromCache(serviceToBeRemoved, userId);
removeServiceFromSystemServicesCache(serviceToBeRemoved, userId);
- removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName()
- .flattenToString(), userId);
+ removeServiceFromMultiModeSettings(
+ serviceToBeRemoved.getComponentName().flattenToString(), userId);
CredentialDescriptionRegistry.forUser(userId)
.evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
}
@@ -286,13 +287,20 @@
}
private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) {
- final int resolvedUserId = ActivityManager.handleIncomingUser(
- Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, false,
- "getPrimaryProvidersForUserId", null);
- SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver(
- context, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
- /* isMultipleMode= */ true);
+ final int resolvedUserId =
+ ActivityManager.handleIncomingUser(
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ userId,
+ false,
+ false,
+ "getPrimaryProvidersForUserId",
+ null);
+ SecureSettingsServiceNameResolver resolver =
+ new SecureSettingsServiceNameResolver(
+ context,
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+ /* isMultipleMode= */ true);
String[] serviceNames = resolver.readServiceNameList(resolvedUserId);
if (serviceNames == null) {
return new HashSet<ComponentName>();
@@ -329,7 +337,8 @@
final long origId = Binder.clearCallingIdentity();
try {
return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
+ DeviceConfig.NAMESPACE_CREDENTIAL,
+ DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
false);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -345,13 +354,14 @@
List<ProviderSession> providerSessions = new ArrayList<>();
for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
activeCredentialContainers) {
- ProviderSession providerSession = ProviderRegistryGetSession.createNewSession(
- mContext,
- UserHandle.getCallingUserId(),
- session,
- session.mClientAppInfo,
- result.second.mPackageName,
- result.first);
+ ProviderSession providerSession =
+ ProviderRegistryGetSession.createNewSession(
+ mContext,
+ UserHandle.getCallingUserId(),
+ session,
+ session.mClientAppInfo,
+ result.second.mPackageName,
+ result.first);
providerSessions.add(providerSession);
session.addProviderSession(providerSession.getComponentName(), providerSession);
}
@@ -367,23 +377,23 @@
List<ProviderSession> providerSessions = new ArrayList<>();
for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
activeCredentialContainers) {
- ProviderSession providerSession = ProviderRegistryGetSession.createNewSession(
- mContext,
- UserHandle.getCallingUserId(),
- session,
- session.mClientAppInfo,
- result.second.mPackageName,
- result.first);
+ ProviderSession providerSession =
+ ProviderRegistryGetSession.createNewSession(
+ mContext,
+ UserHandle.getCallingUserId(),
+ session,
+ session.mClientAppInfo,
+ result.second.mPackageName,
+ result.first);
providerSessions.add(providerSession);
session.addProviderSession(providerSession.getComponentName(), providerSession);
}
return providerSessions;
}
-
@NonNull
private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
- getFilteredResultFromRegistry(List<CredentialOption> options) {
+ getFilteredResultFromRegistry(List<CredentialOption> options) {
// Session for active/provisioned credential descriptions;
CredentialDescriptionRegistry registry =
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
@@ -393,10 +403,12 @@
options.stream()
.map(
getCredentialOption ->
- new HashSet<>(getCredentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(
- CredentialOption.SUPPORTED_ELEMENT_KEYS)))
+ new HashSet<>(
+ getCredentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS)))
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
@@ -408,12 +420,14 @@
for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) {
for (CredentialOption credentialOption : options) {
- Set<String> requestedElementKeys = new HashSet<>(
- credentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
- if (CredentialDescriptionRegistry.checkForMatch(filterResult.mElementKeys,
- requestedElementKeys)) {
+ Set<String> requestedElementKeys =
+ new HashSet<>(
+ credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption.SUPPORTED_ELEMENT_KEYS));
+ if (CredentialDescriptionRegistry.checkForMatch(
+ filterResult.mElementKeys, requestedElementKeys)) {
result.add(new Pair<>(credentialOption, filterResult));
}
}
@@ -449,9 +463,7 @@
}
private CallingAppInfo constructCallingAppInfo(
- String realPackageName,
- int userId,
- @Nullable String origin) {
+ String realPackageName, int userId, @Nullable String origin) {
final PackageInfo packageInfo;
CallingAppInfo callingAppInfo;
try {
@@ -477,8 +489,7 @@
GetCredentialRequest request,
IGetCandidateCredentialsCallback callback,
final String callingPackage) {
- Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
- + callingPackage);
+ Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
final int userId = UserHandle.getCallingUserId();
@@ -496,8 +507,7 @@
request,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
getEnabledProvidersForUser(userId),
- CancellationSignal.fromTransport(cancelTransport)
- );
+ CancellationSignal.fromTransport(cancelTransport));
addSessionLocked(userId, session);
List<ProviderSession> providerSessions =
@@ -531,8 +541,7 @@
IGetCredentialCallback callback,
final String callingPackage) {
final long timestampBegan = System.nanoTime();
- Slog.i(TAG, "starting executeGetCredential with callingPackage: "
- + callingPackage);
+ Slog.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
final int userId = UserHandle.getCallingUserId();
@@ -557,8 +566,7 @@
timestampBegan);
addSessionLocked(userId, session);
- List<ProviderSession> providerSessions =
- prepareProviderSessions(request, session);
+ List<ProviderSession> providerSessions = prepareProviderSessions(request, session);
if (providerSessions.isEmpty()) {
try {
@@ -617,15 +625,17 @@
if (providerSessions.isEmpty()) {
try {
prepareGetCredentialCallback.onResponse(
- new PrepareGetCredentialResponseInternal(PermissionUtils.hasPermission(
- mContext,
- callingPackage,
- Manifest.permission
- .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS),
- /*credentialResultTypes=*/null,
- /*hasAuthenticationResults=*/false,
- /*hasRemoteResults=*/false,
- /*pendingIntent=*/null));
+ new PrepareGetCredentialResponseInternal(
+ PermissionUtils.hasPermission(
+ mContext,
+ callingPackage,
+ Manifest.permission
+ .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS
+ ),
+ /* credentialResultTypes= */ null,
+ /* hasAuthenticationResults= */ false,
+ /* hasRemoteResults= */ false,
+ /* pendingIntent= */ null));
} catch (RemoteException e) {
Slog.e(
TAG,
@@ -641,27 +651,32 @@
}
private List<ProviderSession> prepareProviderSessions(
- GetCredentialRequest request,
- GetRequestSession session) {
+ GetCredentialRequest request, GetRequestSession session) {
List<ProviderSession> providerSessions;
if (isCredentialDescriptionApiEnabled()) {
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
- .filter(credentialOption -> credentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(
- CredentialOption
- .SUPPORTED_ELEMENT_KEYS) != null)
+ .filter(
+ credentialOption ->
+ credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS)
+ != null)
.toList();
List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
request.getCredentialOptions().stream()
- .filter(credentialOption -> credentialOption
- .getCredentialRetrievalData()
- .getStringArrayList(
- CredentialOption
- .SUPPORTED_ELEMENT_KEYS) == null)
+ .filter(
+ credentialOption ->
+ credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS)
+ == null)
.toList();
List<ProviderSession> sessionsWithoutRemoteService =
@@ -706,8 +721,7 @@
ICreateCredentialCallback callback,
String callingPackage) {
final long timestampBegan = System.nanoTime();
- Slog.i(TAG, "starting executeCreateCredential with callingPackage: "
- + callingPackage);
+ Slog.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
if (request.getOrigin() != null) {
@@ -756,8 +770,8 @@
} catch (RemoteException e) {
Slog.e(
TAG,
- "Issue invoking onError on ICreateCredentialCallback "
- + "callback: ", e);
+ "Issue invoking onError on ICreateCredentialCallback " + "callback: ",
+ e);
}
}
@@ -770,8 +784,8 @@
try {
var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric();
initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime());
- MetricUtilities.logApiCalledInitialPhase(initMetric,
- session.mRequestSessionMetric.returnIncrementSequence());
+ MetricUtilities.logApiCalledInitialPhase(
+ initMetric, session.mRequestSessionMetric.returnIncrementSequence());
} catch (Exception e) {
Slog.i(TAG, "Unexpected error during metric logging: ", e);
}
@@ -779,25 +793,32 @@
@Override
public void setEnabledProviders(
- List<String> primaryProviders, List<String> providers, int userId,
+ List<String> primaryProviders,
+ List<String> providers,
+ int userId,
ISetEnabledProvidersCallback callback) {
final int callingUid = Binder.getCallingUid();
if (!hasWriteSecureSettingsPermission()) {
try {
MetricUtilities.logApiCalledSimpleV2(
- ApiName.SET_ENABLED_PROVIDERS,
- ApiStatus.FAILURE, callingUid);
+ ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
callback.onError(
PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
} catch (RemoteException e) {
MetricUtilities.logApiCalledSimpleV2(
- ApiName.SET_ENABLED_PROVIDERS,
- ApiStatus.FAILURE, callingUid);
+ ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
Slog.e(TAG, "Issue with invoking response: ", e);
}
return;
}
+ // If we don't have any primary providers enabled anymore then
+ // we should erase all the providers since the feature is
+ // now disabled.
+ if (primaryProviders.isEmpty()) {
+ providers.clear();
+ }
+
userId =
ActivityManager.handleIncomingUser(
Binder.getCallingPid(),
@@ -808,17 +829,19 @@
"setEnabledProviders",
null);
- Set<String> enableProvider = new HashSet<>(providers);
- enableProvider.addAll(primaryProviders);
+ Set<String> enabledProviders = new HashSet<>(providers);
+ enabledProviders.addAll(primaryProviders);
boolean writeEnabledStatus =
- Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ Settings.Secure.putStringForUser(
+ getContext().getContentResolver(),
Settings.Secure.CREDENTIAL_SERVICE,
- String.join(":", enableProvider),
+ String.join(":", enabledProviders),
userId);
boolean writePrimaryStatus =
- Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ Settings.Secure.putStringForUser(
+ getContext().getContentResolver(),
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
String.join(":", primaryProviders),
userId);
@@ -827,15 +850,13 @@
Slog.e(TAG, "Failed to store setting containing enabled or primary providers");
try {
MetricUtilities.logApiCalledSimpleV2(
- ApiName.SET_ENABLED_PROVIDERS,
- ApiStatus.FAILURE, callingUid);
+ ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
callback.onError(
"failed_setting_store",
"Failed to store setting containing enabled or primary providers");
} catch (RemoteException e) {
MetricUtilities.logApiCalledSimpleV2(
- ApiName.SET_ENABLED_PROVIDERS,
- ApiStatus.FAILURE, callingUid);
+ ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
Slog.e(TAG, "Issue with invoking error response: ", e);
return;
}
@@ -844,13 +865,11 @@
// Call the callback.
try {
MetricUtilities.logApiCalledSimpleV2(
- ApiName.SET_ENABLED_PROVIDERS,
- ApiStatus.SUCCESS, callingUid);
+ ApiName.SET_ENABLED_PROVIDERS, ApiStatus.SUCCESS, callingUid);
callback.onResponse();
} catch (RemoteException e) {
MetricUtilities.logApiCalledSimpleV2(
- ApiName.SET_ENABLED_PROVIDERS,
- ApiStatus.FAILURE, callingUid);
+ ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid);
Slog.e(TAG, "Issue with invoking response: ", e);
// TODO: Propagate failure
}
@@ -859,8 +878,10 @@
@Override
public boolean isEnabledCredentialProviderService(
ComponentName componentName, String callingPackage) {
- Slog.i(TAG, "isEnabledCredentialProviderService with componentName: "
- + componentName.flattenToString());
+ Slog.i(
+ TAG,
+ "isEnabledCredentialProviderService with componentName: "
+ + componentName.flattenToString());
// TODO(253157366): Check additional set of services.
final int userId = UserHandle.getCallingUserId();
@@ -877,7 +898,8 @@
// The component name and the package name do not match.
MetricUtilities.logApiCalledSimpleV2(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.FAILURE, callingUid);
+ ApiStatus.FAILURE,
+ callingUid);
Slog.w(
TAG,
"isEnabledCredentialProviderService: Component name does "
@@ -886,7 +908,8 @@
}
MetricUtilities.logApiCalledSimpleV2(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.SUCCESS, callingUid);
+ ApiStatus.SUCCESS,
+ callingUid);
return true;
}
}
@@ -901,13 +924,14 @@
verifyGetProvidersPermission();
final int callingUid = Binder.getCallingUid();
MetricUtilities.logApiCalledSimpleV2(
- ApiName.GET_CREDENTIAL_PROVIDER_SERVICES,
- ApiStatus.SUCCESS, callingUid);
- return CredentialProviderInfoFactory
- .getCredentialProviderServices(
- mContext, userId, providerFilter, getEnabledProvidersForUser(userId),
- getPrimaryProvidersForUserId(mContext, userId));
+ ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, ApiStatus.SUCCESS, callingUid);
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
+ mContext,
+ userId,
+ providerFilter,
+ getEnabledProvidersForUser(userId),
+ getPrimaryProvidersForUserId(mContext, userId));
}
@Override
@@ -917,7 +941,10 @@
final int userId = UserHandle.getCallingUserId();
return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
- mContext, userId, providerFilter, getEnabledProvidersForUser(userId),
+ mContext,
+ userId,
+ providerFilter,
+ getEnabledProvidersForUser(userId),
getPrimaryProvidersForUserId(mContext, userId));
}
@@ -935,15 +962,22 @@
}
private Set<ComponentName> getEnabledProvidersForUser(int userId) {
- final int resolvedUserId = ActivityManager.handleIncomingUser(
- Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, false,
- "getEnabledProvidersForUser", null);
+ final int resolvedUserId =
+ ActivityManager.handleIncomingUser(
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ userId,
+ false,
+ false,
+ "getEnabledProvidersForUser",
+ null);
Set<ComponentName> enabledProviders = new HashSet<>();
- String directValue = Settings.Secure.getStringForUser(
- mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE,
- resolvedUserId);
+ String directValue =
+ Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.CREDENTIAL_SERVICE,
+ resolvedUserId);
if (!TextUtils.isEmpty(directValue)) {
String[] components = directValue.split(":");
@@ -964,8 +998,7 @@
IClearCredentialStateCallback callback,
String callingPackage) {
final long timestampBegan = System.nanoTime();
- Slog.i(TAG, "starting clearCredentialState with callingPackage: "
- + callingPackage);
+ Slog.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
final int userId = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
enforceCallingPackage(callingPackage, callingUid);
@@ -996,13 +1029,13 @@
if (providerSessions.isEmpty()) {
try {
// TODO("Replace with properly defined error type")
- callback.onError("UNKNOWN", "No credentials available on "
- + "this device");
+ callback.onError("UNKNOWN", "No credentials available on " + "this device");
} catch (RemoteException e) {
Slog.e(
TAG,
"Issue invoking onError on IClearCredentialStateCallback "
- + "callback: ", e);
+ + "callback: ",
+ e);
}
}
@@ -1035,9 +1068,7 @@
public void unregisterCredentialDescription(
UnregisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException {
- Slog.i(TAG, "unregisterCredentialDescription with callingPackage: "
- + callingPackage);
-
+ Slog.i(TAG, "unregisterCredentialDescription with callingPackage: " + callingPackage);
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException("Feature not supported");
@@ -1061,18 +1092,18 @@
}
private void enforcePermissionForAllowedProviders(GetCredentialRequest request) {
- boolean containsAllowedProviders = request.getCredentialOptions()
- .stream()
- .anyMatch(option -> option.getAllowedProviders() != null
- && !option.getAllowedProviders().isEmpty());
+ boolean containsAllowedProviders =
+ request.getCredentialOptions().stream()
+ .anyMatch(
+ option ->
+ option.getAllowedProviders() != null
+ && !option.getAllowedProviders().isEmpty());
if (containsAllowedProviders) {
- mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS,
- null);
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, null);
}
}
- private void addSessionLocked(@UserIdInt int userId,
- RequestSession requestSession) {
+ private void addSessionLocked(@UserIdInt int userId, RequestSession requestSession) {
synchronized (mLock) {
mSessionManager.addSession(userId, requestSession.mRequestId, requestSession);
}
@@ -1080,11 +1111,11 @@
private void enforceCallingPackage(String callingPackage, int callingUid) {
int packageUid;
- PackageManager pm = mContext.createContextAsUser(
- UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+ PackageManager pm =
+ mContext.createContextAsUser(UserHandle.getUserHandleForUid(callingUid), 0)
+ .getPackageManager();
try {
- packageUid = pm.getPackageUid(callingPackage,
- PackageManager.PackageInfoFlags.of(0));
+ packageUid = pm.getPackageUid(callingPackage, PackageManager.PackageInfoFlags.of(0));
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException(callingPackage + " not found");
}
@@ -1110,4 +1141,72 @@
mRequestSessions.get(userId).put(token, requestSession);
}
}
+
+ /** Updates settings when packages are removed. */
+ private final PackageMonitor mPackageMonitor =
+ new PackageMonitor() {
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ Slog.d(TAG, "onPackageRemoved: " + packageName);
+
+ // Remove any providers from the primary setting that contain the package name
+ // being removed.
+ Set<String> primaryProviders =
+ getStoredProviders(
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, packageName);
+ if (!Settings.Secure.putString(
+ getContext().getContentResolver(),
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+ String.join(":", primaryProviders))) {
+ Slog.w(TAG, "Failed to remove primary package: " + packageName);
+ return;
+ }
+
+ // Get the secondary providers and if there are no primary providers then
+ // we should erase all the providers from the secondary list because the
+ // feature is now disabled.
+ if (!primaryProviders.isEmpty()) {
+ return;
+ }
+
+ if (!Settings.Secure.putString(
+ getContext().getContentResolver(),
+ Settings.Secure.CREDENTIAL_SERVICE,
+ "")) {
+ Slog.w(TAG, "Failed to remove secondary package: " + packageName);
+ return;
+ }
+ }
+
+ private Set<String> getStoredProviders(String key, String packageName) {
+ // Get the current providers.
+ String rawProviders =
+ Settings.Secure.getStringForUser(
+ getContext().getContentResolver(), key,
+ UserHandle.myUserId());
+ if (rawProviders == null) {
+ Slog.w(TAG, "settings key is null: " + key);
+ return new HashSet<>();
+ }
+
+ // If the app being removed matches any of the package names from
+ // this list then don't add it in the output.
+ Set<String> providers = new HashSet<>();
+ for (String rawComponentName : rawProviders.split(":")) {
+ if (TextUtils.isEmpty(rawComponentName)
+ || rawComponentName.equals("null")) {
+ Slog.d(TAG, "provider component name is empty or null");
+ continue;
+ }
+
+ ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
+ if (cn != null && !cn.getPackageName().equals(packageName)) {
+ providers.add(cn.flattenToString());
+ }
+ }
+
+ return providers;
+ }
+ };
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index b90f08e..3c190bf 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -32,6 +32,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Slog;
@@ -171,7 +172,9 @@
.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
- return PendingIntent.getActivity(
- mContext, /*requestCode=*/0, intent, PendingIntent.FLAG_IMMUTABLE);
+ return PendingIntent.getActivityAsUser(
+ mContext, /*requestCode=*/0, intent,
+ PendingIntent.FLAG_IMMUTABLE, /*options=*/null,
+ UserHandle.of(mUserId));
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 3eb6718..7bd1cc4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -31,6 +31,7 @@
import android.credentials.ui.GetCredentialProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
+import android.service.autofill.Flags;
import android.service.credentials.Action;
import android.service.credentials.BeginGetCredentialOption;
import android.service.credentials.BeginGetCredentialRequest;
@@ -42,6 +43,7 @@
import android.service.credentials.RemoteEntry;
import android.util.Pair;
import android.util.Slog;
+import android.view.autofill.AutofillId;
import java.util.ArrayList;
import java.util.HashMap;
@@ -379,13 +381,23 @@
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
// those and complete the flow.
- if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
+ Intent intent = new Intent();
+ CredentialOption credentialOption = mBeginGetOptionToCredentialOptionMap.get(id);
+ if (credentialOption == null) {
Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
- return new Intent();
+ return intent;
}
- return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
+ AutofillId autofillId = credentialOption
+ .getCandidateQueryData()
+ .getParcelable(CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId.class);
+ if (autofillId != null && Flags.autofillCredmanIntegration()) {
+ intent.putExtra(CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId);
+ }
+ return intent.putExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
new GetCredentialRequest(
- mCallingAppInfo, List.of(mBeginGetOptionToCredentialOptionMap.get(id))));
+ mCallingAppInfo,
+ List.of(credentialOption)));
}
private Intent setUpFillInIntentWithQueryRequest() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index c63fac9..ee187ba 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -22,6 +22,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -97,6 +100,37 @@
}
@Test
+ public void testRegisterHdrListener() {
+ verify(mMockHdrInfoListener).register(mMockBinder);
+ }
+
+ @Test
+ public void testRegisterOtherHdrListenerWhenCalledWithOtherToken() {
+ IBinder otherBinder = mock(IBinder.class);
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, otherBinder);
+
+ verify(mMockHdrInfoListener).unregister(mMockBinder);
+ verify(mMockHdrInfoListener).register(otherBinder);
+ }
+
+ @Test
+ public void testRegisterHdrListenerOnceWhenCalledWithSameToken() {
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder);
+
+ verify(mMockHdrInfoListener, never()).unregister(mMockBinder);
+ verify(mMockHdrInfoListener, times(1)).register(mMockBinder);
+ }
+
+ @Test
+ public void testRegisterNotCalledIfHbmConfigIsMissing() {
+ IBinder otherBinder = mock(IBinder.class);
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
+
+ verify(mMockHdrInfoListener).unregister(mMockBinder);
+ verify(mMockHdrInfoListener, never()).register(otherBinder);
+ }
+
+ @Test
public void testClamper_AmbientLuxChangesAboveLimit() {
mHdrClamper.onAmbientLuxChange(500);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 0a7bb00..71098aa 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -20,6 +20,7 @@
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL;
import android.platform.test.annotations.Presubmit;
import android.view.KeyEvent;
@@ -284,6 +285,16 @@
KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_H, META_ON}};
}
+ @Keep
+ private static Object[][] shortPressOnSettingsTestArguments() {
+ // testName, testKeys, shortPressOnSettingsBehavior, expectedLogEvent, expectedKey,
+ // expectedModifierState
+ return new Object[][]{
+ {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
+ SHORT_PRESS_SETTINGS_NOTIFICATION_PANEL,
+ KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}};
+ }
+
@Before
public void setUp() {
setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
@@ -294,6 +305,7 @@
mPhoneWindowManager.overrideEnableBugReportTrigger(true);
mPhoneWindowManager.overrideStatusBarManagerInternal();
mPhoneWindowManager.overrideStartActivity();
+ mPhoneWindowManager.overrideSendBroadcast();
mPhoneWindowManager.overrideUserSetupComplete();
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideTogglePanel();
@@ -330,4 +342,15 @@
mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
expectedKey, expectedModifierState, "Failed while executing " + testName);
}
+
+ @Test
+ @Parameters(method = "shortPressOnSettingsTestArguments")
+ public void testShortPressOnSettings(String testName, int[] testKeys,
+ int shortPressOnSettingsBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
+ int expectedModifierState) {
+ mPhoneWindowManager.overrideShortPressOnSettingsBehavior(shortPressOnSettingsBehavior);
+ sendKeyCombination(testKeys, 0 /* duration */);
+ mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
+ expectedKey, expectedModifierState, "Failed while executing " + testName);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index ef28ffa..2244dbe 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -375,6 +375,10 @@
mPhoneWindowManager.mDoubleTapOnHomeBehavior = behavior;
}
+ void overrideShortPressOnSettingsBehavior(int behavior) {
+ mPhoneWindowManager.mShortPressOnSettingsBehavior = behavior;
+ }
+
void overrideCanStartDreaming(boolean canDream) {
doReturn(canDream).when(mDreamManagerInternal).canStartDreaming(anyBoolean());
}
@@ -484,6 +488,10 @@
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
}
+ void overrideSendBroadcast() {
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+ }
+
void overrideUserSetupComplete() {
doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6cebe0a..f3bf026 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -106,6 +106,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
@@ -201,7 +202,8 @@
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
private final Object mLock = new Object();
- Handler mHandler;
+ private Handler mHandler;
+ private Handler mIoHandler;
AppOpsManager mAppOps;
UserManager mUserManager;
PackageManager mPackageManager;
@@ -233,7 +235,7 @@
private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
- @GuardedBy("mLock")
+ @GuardedBy("mLaunchTimeAlarmQueues") // Don't hold the main lock
private final SparseArray<LaunchTimeAlarmQueue> mLaunchTimeAlarmQueues = new SparseArray<>();
@GuardedBy("mUsageEventListeners") // Don't hold the main lock when calling out
private final ArraySet<UsageStatsManagerInternal.UsageEventListener> mUsageEventListeners =
@@ -279,6 +281,38 @@
}
}
+ private final Handler.Callback mIoHandlerCallback = (msg) -> {
+ switch (msg.what) {
+ case MSG_UID_STATE_CHANGED: {
+ final int uid = msg.arg1;
+ final int procState = msg.arg2;
+
+ final int newCounter = (procState <= ActivityManager.PROCESS_STATE_TOP) ? 0 : 1;
+ synchronized (mUidToKernelCounter) {
+ final int oldCounter = mUidToKernelCounter.get(uid, 0);
+ if (newCounter != oldCounter) {
+ mUidToKernelCounter.put(uid, newCounter);
+ try {
+ FileUtils.stringToFile(KERNEL_COUNTER_FILE, uid + " " + newCounter);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to update counter set: " + e);
+ }
+ }
+ }
+ return true;
+ }
+ case MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK: {
+ final int userId = msg.arg1;
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "usageStatsHandleEstimatedLaunchTimesOnUser(" + userId + ")");
+ handleEstimatedLaunchTimesOnUserUnlock(userId);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ return true;
+ }
+ }
+ return false;
+ };
+
private final Injector mInjector;
public UsageStatsService(Context context) {
@@ -299,6 +333,7 @@
mPackageManager = getContext().getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mHandler = new H(BackgroundThread.get().getLooper());
+ mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback);
mAppStandby = mInjector.getAppStandbyController(getContext());
mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby, getContext());
@@ -424,6 +459,9 @@
}
mUserUnlockedStates.remove(userId);
mUserState.put(userId, null); // release the service (mainly for GC)
+ }
+
+ synchronized (mLaunchTimeAlarmQueues) {
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
if (alarmQueue != null) {
alarmQueue.removeAllAlarms();
@@ -479,10 +517,12 @@
}
reportEvent(unlockEvent, userId);
- mHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, userId, 0).sendToTarget();
+ mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK,
+ userId, 0).sendToTarget();
// Remove all the stats stored in system DE.
deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
+
// Force a flush to disk for the current user to ensure important events are persisted.
// Note: there is a very very small chance that the system crashes between deleting
// the stats above from DE and persisting them to CE here in which case we will lose
@@ -601,7 +641,7 @@
private final IUidObserver mUidObserver = new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
+ mIoHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
}
@Override
@@ -673,16 +713,18 @@
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED);
}
- private static void deleteRecursively(File f) {
- File[] files = f.listFiles();
- if (files != null) {
- for (File subFile : files) {
- deleteRecursively(subFile);
+ private static void deleteRecursively(final File path) {
+ if (path.isDirectory()) {
+ final File[] files = path.listFiles();
+ if (files != null) {
+ for (File subFile : files) {
+ deleteRecursively(subFile);
+ }
}
}
- if (f.exists() && !f.delete()) {
- Slog.e(TAG, "Failed to delete " + f);
+ if (path.exists() && !path.delete()) {
+ Slog.e(TAG, "Failed to delete " + path);
}
}
@@ -1244,6 +1286,9 @@
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
mAppTimeLimit.onUserRemoved(userId);
+ }
+
+ synchronized (mLaunchTimeAlarmQueues) {
final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
if (alarmQueue != null) {
alarmQueue.removeAllAlarms();
@@ -1274,6 +1319,13 @@
}
}
+ synchronized (mLaunchTimeAlarmQueues) {
+ final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
+ if (alarmQueue != null) {
+ alarmQueue.removeAlarmForKey(packageName);
+ }
+ }
+
final int tokenRemoved;
synchronized (mLock) {
final long timeRemoved = System.currentTimeMillis();
@@ -1282,10 +1334,7 @@
// when the user service is initialized and package manager is queried.
return;
}
- final LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
- if (alarmQueue != null) {
- alarmQueue.removeAlarmForKey(packageName);
- }
+
final UserUsageStatsService userService = mUserState.get(userId);
if (userService == null) {
return;
@@ -1495,60 +1544,63 @@
estimatedLaunchTime = calculateEstimatedPackageLaunchTime(userId, packageName);
mAppStandby.setEstimatedLaunchTime(packageName, userId, estimatedLaunchTime);
- synchronized (mLock) {
- LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
- if (alarmQueue == null) {
- alarmQueue = new LaunchTimeAlarmQueue(
- userId, getContext(), BackgroundThread.get().getLooper());
- mLaunchTimeAlarmQueues.put(userId, alarmQueue);
- }
- alarmQueue.addAlarm(packageName,
- SystemClock.elapsedRealtime() + (estimatedLaunchTime - now));
- }
+ getOrCreateLaunchTimeAlarmQueue(userId).addAlarm(packageName,
+ SystemClock.elapsedRealtime() + (estimatedLaunchTime - now));
}
return estimatedLaunchTime;
}
+ private LaunchTimeAlarmQueue getOrCreateLaunchTimeAlarmQueue(int userId) {
+ synchronized (mLaunchTimeAlarmQueues) {
+ LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
+ if (alarmQueue == null) {
+ alarmQueue = new LaunchTimeAlarmQueue(
+ userId, getContext(), BackgroundThread.get().getLooper());
+ mLaunchTimeAlarmQueues.put(userId, alarmQueue);
+ }
+
+ return alarmQueue;
+ }
+ }
+
@CurrentTimeMillisLong
private long calculateEstimatedPackageLaunchTime(int userId, String packageName) {
- synchronized (mLock) {
- final long endTime = System.currentTimeMillis();
- final long beginTime = endTime - ONE_WEEK;
- final long unknownTime = endTime + UNKNOWN_LAUNCH_TIME_DELAY_MS;
- final UsageEvents events = queryEarliestEventsForPackage(
- userId, beginTime, endTime, packageName, Event.ACTIVITY_RESUMED);
- if (events == null) {
- if (DEBUG) {
- Slog.d(TAG, "No events for " + userId + ":" + packageName);
- }
- return unknownTime;
+ final long endTime = System.currentTimeMillis();
+ final long beginTime = endTime - ONE_WEEK;
+ final long unknownTime = endTime + UNKNOWN_LAUNCH_TIME_DELAY_MS;
+ final UsageEvents events = queryEarliestEventsForPackage(
+ userId, beginTime, endTime, packageName, Event.ACTIVITY_RESUMED);
+ if (events == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "No events for " + userId + ":" + packageName);
}
- final UsageEvents.Event event = new UsageEvents.Event();
- final boolean hasMoreThan24HoursOfHistory;
- if (events.getNextEvent(event)) {
- hasMoreThan24HoursOfHistory = endTime - event.getTimeStamp() > ONE_DAY;
- if (DEBUG) {
- Slog.d(TAG, userId + ":" + packageName + " history > 24 hours="
- + hasMoreThan24HoursOfHistory);
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, userId + ":" + packageName + " has no events");
- }
- return unknownTime;
- }
- do {
- if (event.getEventType() == Event.ACTIVITY_RESUMED) {
- final long timestamp = event.getTimeStamp();
- final long nextLaunch =
- calculateNextLaunchTime(hasMoreThan24HoursOfHistory, timestamp);
- if (nextLaunch > endTime) {
- return nextLaunch;
- }
- }
- } while (events.getNextEvent(event));
return unknownTime;
}
+ final UsageEvents.Event event = new UsageEvents.Event();
+ final boolean hasMoreThan24HoursOfHistory;
+ if (events.getNextEvent(event)) {
+ hasMoreThan24HoursOfHistory = endTime - event.getTimeStamp() > ONE_DAY;
+ if (DEBUG) {
+ Slog.d(TAG, userId + ":" + packageName + " history > 24 hours="
+ + hasMoreThan24HoursOfHistory);
+ }
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, userId + ":" + packageName + " has no events");
+ }
+ return unknownTime;
+ }
+ do {
+ if (event.getEventType() == Event.ACTIVITY_RESUMED) {
+ final long timestamp = event.getTimeStamp();
+ final long nextLaunch =
+ calculateNextLaunchTime(hasMoreThan24HoursOfHistory, timestamp);
+ if (nextLaunch > endTime) {
+ return nextLaunch;
+ }
+ }
+ } while (events.getNextEvent(event));
+ return unknownTime;
}
@CurrentTimeMillisLong
@@ -1569,61 +1621,54 @@
}
private void handleEstimatedLaunchTimesOnUserUnlock(int userId) {
- synchronized (mLock) {
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long now = System.currentTimeMillis();
- final long beginTime = now - ONE_WEEK;
- final UsageEvents events = queryEarliestAppEvents(
- userId, beginTime, now, Event.ACTIVITY_RESUMED);
- if (events == null) {
- return;
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long now = System.currentTimeMillis();
+ final long beginTime = now - ONE_WEEK;
+ final UsageEvents events = queryEarliestAppEvents(
+ userId, beginTime, now, Event.ACTIVITY_RESUMED);
+ if (events == null) {
+ return;
+ }
+ final ArrayMap<String, Boolean> hasMoreThan24HoursOfHistory = new ArrayMap<>();
+ final UsageEvents.Event event = new UsageEvents.Event();
+ boolean changedTimes = false;
+ final LaunchTimeAlarmQueue alarmQueue = getOrCreateLaunchTimeAlarmQueue(userId);
+ for (boolean unprocessedEvent = events.getNextEvent(event); unprocessedEvent;
+ unprocessedEvent = events.getNextEvent(event)) {
+ final String packageName = event.getPackageName();
+ if (!hasMoreThan24HoursOfHistory.containsKey(packageName)) {
+ boolean hasHistory = now - event.getTimeStamp() > ONE_DAY;
+ if (DEBUG) {
+ Slog.d(TAG,
+ userId + ":" + packageName + " history > 24 hours=" + hasHistory);
+ }
+ hasMoreThan24HoursOfHistory.put(packageName, hasHistory);
}
- final ArrayMap<String, Boolean> hasMoreThan24HoursOfHistory = new ArrayMap<>();
- final UsageEvents.Event event = new UsageEvents.Event();
- LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
- if (alarmQueue == null) {
- alarmQueue = new LaunchTimeAlarmQueue(
- userId, getContext(), BackgroundThread.get().getLooper());
- mLaunchTimeAlarmQueues.put(userId, alarmQueue);
- }
- boolean changedTimes = false;
- for (boolean unprocessedEvent = events.getNextEvent(event); unprocessedEvent;
- unprocessedEvent = events.getNextEvent(event)) {
- final String packageName = event.getPackageName();
- if (!hasMoreThan24HoursOfHistory.containsKey(packageName)) {
- boolean hasHistory = now - event.getTimeStamp() > ONE_DAY;
+ if (event.getEventType() == Event.ACTIVITY_RESUMED) {
+ long estimatedLaunchTime =
+ mAppStandby.getEstimatedLaunchTime(packageName, userId);
+ if (estimatedLaunchTime < now || estimatedLaunchTime == Long.MAX_VALUE) {
+ //noinspection ConstantConditions
+ estimatedLaunchTime = calculateNextLaunchTime(
+ hasMoreThan24HoursOfHistory.get(packageName), event.getTimeStamp());
+ mAppStandby.setEstimatedLaunchTime(
+ packageName, userId, estimatedLaunchTime);
+ }
+ if (estimatedLaunchTime < now + ONE_WEEK) {
+ // Before a user is unlocked, we don't know when the app will be launched,
+ // so we give callers the UNKNOWN time. Now that we have a better estimate,
+ // we should notify them of the change.
if (DEBUG) {
- Slog.d(TAG,
- userId + ":" + packageName + " history > 24 hours=" + hasHistory);
+ Slog.d(TAG, "User " + userId + " unlock resulting in"
+ + " estimated launch time change for " + packageName);
}
- hasMoreThan24HoursOfHistory.put(packageName, hasHistory);
+ changedTimes |= stageChangedEstimatedLaunchTime(userId, packageName);
}
- if (event.getEventType() == Event.ACTIVITY_RESUMED) {
- long estimatedLaunchTime =
- mAppStandby.getEstimatedLaunchTime(packageName, userId);
- if (estimatedLaunchTime < now || estimatedLaunchTime == Long.MAX_VALUE) {
- //noinspection ConstantConditions
- estimatedLaunchTime = calculateNextLaunchTime(
- hasMoreThan24HoursOfHistory.get(packageName), event.getTimeStamp());
- mAppStandby.setEstimatedLaunchTime(
- packageName, userId, estimatedLaunchTime);
- }
- if (estimatedLaunchTime < now + ONE_WEEK) {
- // Before a user is unlocked, we don't know when the app will be launched,
- // so we give callers the UNKNOWN time. Now that we have a better estimate,
- // we should notify them of the change.
- if (DEBUG) {
- Slog.d(TAG, "User " + userId + " unlock resulting in"
- + " estimated launch time change for " + packageName);
- }
- changedTimes |= stageChangedEstimatedLaunchTime(userId, packageName);
- }
- alarmQueue.addAlarm(packageName, nowElapsed + (estimatedLaunchTime - now));
- }
+ alarmQueue.addAlarm(packageName, nowElapsed + (estimatedLaunchTime - now));
}
- if (changedTimes) {
- mHandler.sendEmptyMessage(MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED);
- }
+ }
+ if (changedTimes) {
+ mHandler.sendEmptyMessage(MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED);
}
}
@@ -1995,37 +2040,11 @@
case MSG_PACKAGE_REMOVED:
onPackageRemoved(msg.arg1, (String) msg.obj);
break;
- case MSG_UID_STATE_CHANGED: {
- final int uid = msg.arg1;
- final int procState = msg.arg2;
-
- final int newCounter = (procState <= ActivityManager.PROCESS_STATE_TOP) ? 0 : 1;
- synchronized (mUidToKernelCounter) {
- final int oldCounter = mUidToKernelCounter.get(uid, 0);
- if (newCounter != oldCounter) {
- mUidToKernelCounter.put(uid, newCounter);
- try {
- FileUtils.stringToFile(KERNEL_COUNTER_FILE, uid + " " + newCounter);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to update counter set: " + e);
- }
- }
- }
- break;
- }
case MSG_ON_START:
synchronized (mLock) {
loadGlobalComponentUsageLocked();
}
break;
- case MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK: {
- final int userId = msg.arg1;
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
- "usageStatsHandleEstimatedLaunchTimesOnUser(" + userId + ")");
- handleEstimatedLaunchTimesOnUserUnlock(userId);
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- }
- break;
case MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED: {
removeMessages(MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 7d9b379..a7675d6 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1894,7 +1894,7 @@
* Tones are both played locally for the user to hear and sent to the network to be relayed
* to the remote device.
* <p>
- * You must ensure that any call to {@link #playDtmfTone(char}) is followed by a matching
+ * You must ensure that any call to {@link #playDtmfTone(char)} is followed by a matching
* call to {@link #stopDtmfTone()} and that each tone is stopped before a new one is started.
* The play and stop commands are relayed to the underlying
* {@link android.telecom.ConnectionService} as executed; implementations may not correctly
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 50f2ad4..24d3918 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -225,7 +225,7 @@
/**
* Request start a call streaming session. On receiving valid request, telecom will bind to
- * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
+ * the {@code CallStreamingService} implemented by a general call streaming sender. So that the
* call streaming sender can perform streaming local device audio to another remote device and
* control the call during streaming.
*
diff --git a/telecomm/java/android/telecom/CallControlCallback.java b/telecomm/java/android/telecom/CallControlCallback.java
index eac2e64..0166022 100644
--- a/telecomm/java/android/telecom/CallControlCallback.java
+++ b/telecomm/java/android/telecom/CallControlCallback.java
@@ -69,7 +69,7 @@
/**
* Telecom is informing the client to answer an incoming call and set it to active.
*
- * @param videoState see {@link android.telecom.CallAttributes.CallType} for valid states
+ * @param videoState the video state
* @param wasCompleted The {@link Consumer} to be completed. If the client can answer the call
* on their end, {@link Consumer#accept(Object)} should be called with
* {@link Boolean#TRUE}.
diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
index d9f89d5..c83804f 100644
--- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -68,7 +68,7 @@
/**
* Creates a new instance of {@link PhoneAccountSuggestion}. This constructor is intended for
- * use by apps implementing a {@link PhoneAccountSuggestionService}, and generally should not be
+ * use by apps implementing a {@code PhoneAccountSuggestionService}, and generally should not be
* used by dialer apps other than for testing purposes.
*
* @param handle The {@link PhoneAccountHandle} for this suggestion.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 98bbb40..63e91ad 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5073,7 +5073,6 @@
* MMTEL and RCS.
* <p>
* The default value for this configuration is {@code false}.
- * @see android.telephony.ims.SipDelegateManager
*/
public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL =
KEY_PREFIX + "ims_single_registration_required_bool";
@@ -5648,8 +5647,8 @@
* <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li>
* <li>{@link #KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY}</li>
* </ul>
- * <p> The values are defined in
- * {@link ImsRegistrationImplBase.ImsRegistrationTech}
+ * <p> The values are defined as {@code REGISTRATION_TECH_*} constants in
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase}.
*
* changing mmtel_requires_provisioning_bundle requires changes to
* carrier_volte_provisioning_required_bool and vice versa
@@ -5661,12 +5660,12 @@
/**
* List of different RAT technologies on which Provisioning for Voice calling (IR.92)
* is supported.
- * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+ * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE
*/
public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY =
KEY_PREFIX + "capability_type_voice_int_array";
@@ -5674,12 +5673,12 @@
/**
* List of different RAT technologies on which Provisioning for Video Telephony (IR.94)
* is supported.
- * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+ * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO
*/
public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY =
KEY_PREFIX + "capability_type_video_int_array";
@@ -5687,24 +5686,24 @@
/**
* List of different RAT technologies on which Provisioning for XCAP over Ut for
* supplementary services. (IR.92) is supported.
- * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+ * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT
*/
public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY =
KEY_PREFIX + "capability_type_ut_int_array";
/**
* List of different RAT technologies on which Provisioning for SMS (IR.92) is supported.
- * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+ * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS
*/
public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY =
KEY_PREFIX + "capability_type_sms_int_array";
@@ -5712,12 +5711,12 @@
/**
* List of different RAT technologies on which Provisioning for Call Composer
* (section 2.4 of RCC.20) is supported.
- * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
+ * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER
*/
public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY =
KEY_PREFIX + "capability_type_call_composer_int_array";
@@ -5732,8 +5731,8 @@
* <li>{@link #KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY}</li>
* <li>{@link #KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY}</li>
* </ul>
- * <p> The values are defined in
- * {@link ImsRegistrationImplBase.ImsRegistrationTech}
+ * <p> The values are defined as {@code REGISTRATION_TECH_*} constants in
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase}.
*/
public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE =
KEY_PREFIX + "rcs_requires_provisioning_bundle";
@@ -5742,12 +5741,11 @@
* This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
* framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
* If not set, this RcsFeature should not service capability requests.
- * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
*/
public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY =
KEY_PREFIX + "capability_type_options_uce_int_array";
@@ -5757,12 +5755,11 @@
* framework. If set, the RcsFeature should support capability exchange using a presence
* server. If not set, this RcsFeature should not publish capabilities or service capability
* requests using presence.
- * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE
* <p>Possible values are,
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM}
- * {@link ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_CROSS_SIM}
+ * {@link android.telephony.ims.stub.ImsRegistrationImplBase#REGISTRATION_TECH_NR}
*/
public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY =
KEY_PREFIX + "capability_type_presence_uce_int_array";
@@ -9640,7 +9637,7 @@
/**
* A list of premium capabilities the carrier supports. Applications can prompt users to
* purchase these premium capabilities from their carrier for a performance boost.
- * Valid values are any of {@link TelephonyManager.PremiumCapability}.
+ * Valid values are any of {@link TelephonyManager}'s {@code PREMIUM_CAPABILITY_*} constants.
*
* This is empty by default, indicating that no premium capabilities are supported.
*
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 631013f..cb4a6e7 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -625,7 +625,7 @@
}
/**
- * @return The access network technology {@link NetworkType}.
+ * @return The access network technology network type..
*/
public @NetworkType int getAccessNetworkTechnology() {
return mAccessNetworkTechnology;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index fa5fd87..8e90fe7 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1364,7 +1364,7 @@
public static class OnSubscriptionsChangedListener {
/**
- * After {@link Build.VERSION_CODES.Q}, it is no longer necessary to instantiate a
+ * After {@link Build.VERSION_CODES#Q}, it is no longer necessary to instantiate a
* Handler inside of the OnSubscriptionsChangedListener in all cases, so it will only
* be done for callers that do not supply an Executor.
*/
@@ -1388,13 +1388,14 @@
/**
* Create an OnSubscriptionsChangedListener.
*
- * For callers targeting {@link Build.VERSION_CODES.P} or earlier, this can only be called
+ * For callers targeting {@link Build.VERSION_CODES#P} or earlier, this can only be called
* on a thread that already has a prepared Looper. Callers targeting Q or later should
* subsequently use {@link SubscriptionManager#addOnSubscriptionsChangedListener(
* Executor, OnSubscriptionsChangedListener)}.
*
- * On OS versions prior to {@link Build.VERSION_CODES.V} callers should assume that this
- * call will fail if invoked on a thread that does not already have a prepared looper.
+ * On OS versions prior to {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} callers should
+ * assume that this call will fail if invoked on a thread that does not already have a
+ * prepared looper.
*/
public OnSubscriptionsChangedListener() {
mCreatorLooper = Looper.myLooper();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 234ca91..548fa97 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13132,8 +13132,8 @@
* </ul>
*
* @param executor The executor on which the result listener will be called.
- * @param resultListener {@link Consumer} that will be called with the result fetched
- * from the radio of type {@link CarrierRestrictionStatus}
+ * @param resultListener {@link Consumer} that will be called with the carrier restriction
+ * status result fetched from the radio
* @throws SecurityException if the caller does not have the required permission/privileges or
* if the caller is not pre-registered.
*/
@@ -13465,7 +13465,7 @@
public static final int DATA_ENABLED_REASON_THERMAL = 3;
/**
- * To indicate data was enabled or disabled due to {@link MobileDataPolicy} overrides.
+ * To indicate data was enabled or disabled due to mobile data policy overrides.
* Note that this is not a valid reason for {@link #setDataEnabledForReason(int, boolean)} and
* is only used to indicate that data enabled was changed due to an override.
*/
@@ -14571,7 +14571,7 @@
* @param needValidation whether validation is needed before switch happens.
* @param executor The executor of where the callback will execute.
* @param callback Callback will be triggered once it succeeds or failed.
- * See {@link TelephonyManager.SetOpportunisticSubscriptionResult}
+ * See the {@code SET_OPPORTUNISTIC_SUB_*} constants
* for more details. Pass null if don't care about the result.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -17516,7 +17516,7 @@
public @interface PurchasePremiumCapabilityResult {}
/**
- * Returns the purchase result {@link PurchasePremiumCapabilityResult} as a String.
+ * Returns the purchase result as a String.
*
* @param result The purchase premium capability result.
* @return The purchase result as a String.
@@ -17570,7 +17570,6 @@
* @param capability The premium capability to purchase.
* @param executor The callback executor for the response.
* @param callback The result of the purchase request.
- * One of {@link PurchasePremiumCapabilityResult}.
* @throws SecurityException if the caller does not hold permissions
* READ_BASIC_PHONE_STATE or INTERNET.
* @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid.
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 26c17a4..e9af486 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -532,7 +532,7 @@
/**
* Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
* up by this APN setting. Note this value will only be used when MTU size is not provided
- * in {@link DataCallResponse#getMtuV4()} during network bring up.
+ * in {@code DataCallResponse#getMtuV4()} during network bring up.
*
* @return the MTU size in bytes of the route.
*/
@@ -542,7 +542,7 @@
/**
* Returns the MTU size of the IPv6 mobile interface to which the APN connected. Note this value
- * will only be used when MTU size is not provided in {@link DataCallResponse#getMtuV6()}
+ * will only be used when MTU size is not provided in {@code DataCallResponse#getMtuV6()}
* during network bring up.
*
* @return the MTU size in bytes of the route.
@@ -1787,7 +1787,7 @@
/**
* Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
* up by this APN setting. Note this value will only be used when MTU size is not provided
- * in {@link DataCallResponse#getMtuV4()} during network bring up.
+ * in {@code DataCallResponse#getMtuV4()} during network bring up.
*
* @param mtuV4 the MTU size in bytes of the route.
*/
@@ -1799,7 +1799,7 @@
/**
* Set the default MTU (Maximum Transmission Unit) size in bytes of the IPv6 routes brought
* up by this APN setting. Note this value will only be used when MTU size is not provided
- * in {@link DataCallResponse#getMtuV6()} during network bring up.
+ * in {@code DataCallResponse#getMtuV6()} during network bring up.
*
* @param mtuV6 the MTU size in bytes of the route.
*/
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index ed46276..b9a7d43 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -249,7 +249,7 @@
*
* <p>{@link #EXTRA_USE_QR_SCANNER} not set or set to false: The LPA should try to get an
* activation code from the carrier app by binding to the carrier app service implementing
- * {@link android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE}.
+ * {@code android.service.euicc.EuiccService#ACTION_BIND_CARRIER_PROVISIONING_SERVICE}.
* <p>{@link #EXTRA_USE_QR_SCANNER} set to true: The LPA should launch a QR scanner for the user
* to scan an eSIM profile QR code.
*
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index caee4e2..2172d7d 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -561,7 +561,7 @@
* @param c The MmTel {@link CapabilityCallback} to be registered.
* @see #unregisterMmTelCapabilityCallback(CapabilityCallback)
* @throws ImsException if the subscription associated with this callback is valid, but
- * the {@link ImsService} associated with the subscription is not available. This can happen if
+ * the {@code ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
*/
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 4439e5c..2b49bcd 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -247,7 +247,7 @@
* @param c The {@link RegistrationManager.RegistrationCallback} to be added.
* @see #unregisterImsRegistrationCallback(RegistrationManager.RegistrationCallback)
* @throws ImsException if the subscription associated with this callback is valid, but
- * the {@link ImsService} associated with the subscription is not available. This can happen if
+ * the {@code ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
*/
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 37a6a7e..1c5d1e9 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1469,9 +1469,8 @@
/**
* Get the provisioning status for the IMS MmTel capability specified.
*
- * If provisioning is not required for the queried
- * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and
- * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
+ * If provisioning is not required for the queried {@code capability} and
+ * {@code tech} combination specified, this method will
* always return {@code true}.
*
* <p> Requires Permission:
@@ -1503,7 +1502,7 @@
* Get the provisioning status for the IMS RCS capability specified.
*
* If provisioning is not required for the queried
- * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS
+ * {@code capability} or if the device does not support IMS
* this method will always return {@code true}.
*
* @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
@@ -1533,7 +1532,7 @@
* Get the provisioning status for the IMS RCS capability specified.
*
* If provisioning is not required for the queried
- * {@link ImsRcsManager.RcsImsCapabilityFlag} or if the device does not support IMS
+ * {@code capability} or if the device does not support IMS
* this method will always return {@code true}.
*
* <p> Requires Permission:
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 873ce60..b528866 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -31,7 +31,6 @@
import android.telephony.AccessNetworkConstants;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ims.aidl.IImsRegistrationCallback;
-import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
@@ -42,7 +41,7 @@
import java.util.function.Consumer;
/**
- * Manages IMS Service registration state for associated {@link ImsFeature}s.
+ * Manages IMS Service registration state for associated {@code ImsFeature}s.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public interface RegistrationManager {
@@ -394,7 +393,7 @@
* @param c The {@link RegistrationCallback} to be added.
* @see #unregisterImsRegistrationCallback(RegistrationCallback)
* @throws ImsException if the subscription associated with this callback is valid, but
- * the {@link ImsService} associated with the subscription is not available. This can happen if
+ * the {@code ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
*/
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index f910b8b..cf2d5d6 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -9,6 +9,10 @@
android_test {
name: "InputTests",
+ defaults: [
+ // For ExtendedMockito dependencies.
+ "modules-utils-testable-device-config-defaults",
+ ],
srcs: [
"src/**/*.java",
"src/**/*.kt",
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index b6477510..fa86e9c 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -32,11 +32,16 @@
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.provider.Settings
+import android.util.proto.ProtoOutputStream
import android.view.InputDevice
import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodSubtype
import androidx.test.core.R
import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.os.KeyboardConfiguredProto
+import com.android.internal.util.FrameworkStatsLog
+import com.android.modules.utils.testing.ExtendedMockitoRule
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
@@ -46,9 +51,9 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
@@ -96,6 +101,9 @@
private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
+ const val LAYOUT_TYPE_QWERTZ = 2
+ const val LAYOUT_TYPE_QWERTY = 1
+ const val LAYOUT_TYPE_DEFAULT = 0
}
private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
@@ -103,8 +111,10 @@
private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
- @get:Rule
- val rule = MockitoJUnit.rule()!!
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(FrameworkStatsLog::class.java).build()!!
@Mock
private lateinit var iInputManager: IInputManager
@@ -145,7 +155,9 @@
override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
})
testLooper = TestLooper()
- keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+ keyboardLayoutManager = Mockito.spy(
+ KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+ )
setupInputDevices()
setupBroadcastReceiver()
setupIme()
@@ -827,6 +839,100 @@
}
}
+ @Test
+ fun testConfigurationLogged_onInputDeviceAdded_VirtualKeyboardBasedSelection() {
+ val imeInfos = listOf(
+ KeyboardLayoutManager.ImeInfo(0, imeInfo,
+ createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
+ Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+ NewSettingsApiFlag(true).use {
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ "German",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testConfigurationLogged_onInputDeviceAdded_DeviceBasedSelection() {
+ val imeInfos = listOf(
+ KeyboardLayoutManager.ImeInfo(0, imeInfo,
+ createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
+ Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+ NewSettingsApiFlag(true).use {
+ keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
+ ArgumentMatchers.eq(createByteArray(
+ "en",
+ LAYOUT_TYPE_QWERTY,
+ "English (US)",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
+ val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+ Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+ NewSettingsApiFlag(true).use {
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ "Default",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT))
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testConfigurationNotLogged_onInputDeviceChanged() {
+ val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
+ Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
+ NewSettingsApiFlag(true).use {
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify({
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(ByteArray::class.java)
+ )
+ }, Mockito.times(0))
+ }
+ }
+
private fun assertCorrectLayout(
device: InputDevice,
imeSubtype: InputMethodSubtype,
@@ -842,18 +948,60 @@
}
private fun createImeSubtype(): InputMethodSubtype =
- InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
+ createImeSubtypeForLanguageTagAndLayoutType(null, null)
private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
- InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
- .setLanguageTag(languageTag).build()
+ createImeSubtypeForLanguageTagAndLayoutType(languageTag, null)
private fun createImeSubtypeForLanguageTagAndLayoutType(
- languageTag: String,
- layoutType: String
- ): InputMethodSubtype =
- InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
- .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+ languageTag: String?,
+ layoutType: String?
+ ): InputMethodSubtype {
+ val builder = InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeId(nextImeSubtypeId++)
+ .setIsAuxiliary(false)
+ .setSubtypeMode("keyboard")
+ if (languageTag != null && layoutType != null) {
+ builder.setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType)
+ } else if (languageTag != null) {
+ builder.setLanguageTag(languageTag)
+ }
+ return builder.build()
+ }
+
+ private fun createByteArray(
+ expectedLanguageTag: String, expectedLayoutType: Int, expectedLayoutName: String,
+ expectedCriteria: Int, expectedImeLanguageTag: String, expectedImeLayoutType: Int): ByteArray {
+ val proto = ProtoOutputStream()
+ val keyboardLayoutConfigToken = proto.start(
+ KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG)
+ proto.write(
+ KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LANGUAGE_TAG,
+ expectedLanguageTag
+ )
+ proto.write(
+ KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LAYOUT_TYPE,
+ expectedLayoutType
+ )
+ proto.write(
+ KeyboardConfiguredProto.KeyboardLayoutConfig.KEYBOARD_LAYOUT_NAME,
+ expectedLayoutName
+ )
+ proto.write(
+ KeyboardConfiguredProto.KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
+ expectedCriteria
+ )
+ proto.write(
+ KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+ expectedImeLanguageTag
+ )
+ proto.write(
+ KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+ expectedImeLayoutType
+ )
+ proto.end(keyboardLayoutConfigToken);
+ return proto.bytes
+ }
private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
for (kl in layoutList) {
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7323b0f..977b276 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -220,6 +220,7 @@
name: "aapt2-protos",
tools: [":soong_zip"],
srcs: [
+ "ApkInfo.proto",
"Configuration.proto",
"ResourcesInternal.proto",
"ResourceMetadata.proto",
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 87da09a..8c644cf 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -49,16 +49,19 @@
kDeprecated = 0x01,
kSystemApi = 0x02,
kTestApi = 0x04,
+ kFlaggedApi = 0x08,
};
StringPiece doc_str;
uint32_t bit_mask;
StringPiece annotation;
+ bool preserve_params;
};
-static std::array<AnnotationRule, 2> sAnnotationRules = {{
- {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi"},
- {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi"},
+static std::array<AnnotationRule, 3> sAnnotationRules = {{
+ {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi", true},
+ {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi", false},
+ {"@FlaggedApi", AnnotationRule::kFlaggedApi, "@android.annotation.FlaggedApi", true},
}};
void AnnotationProcessor::AppendCommentLine(std::string comment) {
@@ -73,12 +76,11 @@
std::string::size_type idx = comment.find(rule.doc_str.data());
if (idx != std::string::npos) {
// Captures all parameters associated with the specified annotation rule
- // by matching the first pair of parantheses after the rule.
- std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)");
+ // by matching the first pair of parentheses after the rule.
+ std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))"));
std::smatch match_result;
const bool is_match = std::regex_search(comment, match_result, re);
- // We currently only capture and preserve parameters for SystemApi.
- if (is_match && rule.bit_mask == AnnotationRule::kSystemApi) {
+ if (is_match && rule.preserve_params) {
annotation_parameter_map_[rule.bit_mask] = match_result[1].str();
comment.erase(comment.begin() + match_result.position(),
comment.begin() + match_result.position() + match_result.length());
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index 6bc8902..e98e96b 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -76,6 +76,36 @@
EXPECT_THAT(annotations, HasSubstr("This is a system API"));
}
+TEST(AnnotationProcessorTest, EmitsFlaggedApiAnnotationAndRemovesFromComment) {
+ AnnotationProcessor processor;
+ processor.AppendComment("@FlaggedApi This is a flagged API");
+
+ std::string annotations;
+ StringOutputStream out(&annotations);
+ Printer printer(&out);
+ processor.Print(&printer);
+ out.Flush();
+
+ EXPECT_THAT(annotations, HasSubstr("@android.annotation.FlaggedApi"));
+ EXPECT_THAT(annotations, Not(HasSubstr("@FlaggedApi")));
+ EXPECT_THAT(annotations, HasSubstr("This is a flagged API"));
+}
+
+TEST(AnnotationProcessorTest, EmitsFlaggedApiAnnotationParamsAndRemovesFromComment) {
+ AnnotationProcessor processor;
+ processor.AppendComment("@FlaggedApi (\"android.flags.my_flag\") This is a flagged API");
+
+ std::string annotations;
+ StringOutputStream out(&annotations);
+ Printer printer(&out);
+ processor.Print(&printer);
+ out.Flush();
+
+ EXPECT_THAT(annotations, HasSubstr("@android.annotation.FlaggedApi(\"android.flags.my_flag\")"));
+ EXPECT_THAT(annotations, Not(HasSubstr("@FlaggedApi")));
+ EXPECT_THAT(annotations, HasSubstr("This is a flagged API"));
+}
+
TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) {
AnnotationProcessor processor;
processor.AppendComment("@TestApi This is a test API");