Merge "Respect no animation request when calling moveTaskToFront." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a1375d7..605ce22 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -33,6 +33,7 @@
":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
":android.widget.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.audio.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":sdk_sandbox_flags_lib{.generated_srcjars}",
":android.permission.flags-aconfig-java{.generated_srcjars}",
@@ -46,6 +47,7 @@
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":aconfig_midi_flags_java_lib{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
]
@@ -317,6 +319,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media Audio
+java_aconfig_library {
+ name: "com.android.media.audio.flags-aconfig-java",
+ aconfig_declarations: "aconfig_audio_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Permissions
aconfig_declarations {
name: "android.permission.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index a507465a..0c199a6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -227,7 +227,6 @@
"android.hardware.radio.messaging-V3-java",
"android.hardware.radio.modem-V3-java",
"android.hardware.radio.network-V3-java",
- "android.hardware.radio.satellite-V1-java",
"android.hardware.radio.sim-V3-java",
"android.hardware.radio.voice-V3-java",
"android.hardware.thermal-V1.0-java-constants",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 9218cc9..da02298 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -59,6 +59,7 @@
// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
java_genrule_host {
name: "framework-minus-apex.ravenwood",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-minus-apex.ravenwood-base{ravenwood.jar}",
@@ -66,5 +67,4 @@
out: [
"framework-minus-apex.ravenwood.jar",
],
- visibility: ["//visibility:public"],
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index f1403bd5..e833bb9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -439,7 +439,7 @@
* provides an easy way to tell whether the job is being executed due to the deadline
* expiring. Note: If the job is running because its deadline expired, it implies that its
* constraints will not be met. However,
- * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever
+ * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever
* run when their constraints are satisfied, therefore, the constraints will still be satisfied
* for a periodic job even if the deadline has expired.
*/
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 30b4423..e162100 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -124,7 +124,6 @@
"packages/modules/Media/apex/aidl/stable",
],
},
- extensions_info_file: ":sdk-extensions-info",
}
droidstubs {
@@ -132,13 +131,7 @@
defaults: ["framework-doc-stubs-sources-default"],
args: metalava_framework_docs_args +
" --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- api_levels_sdk_type: "system",
- extensions_info_file: ":sdk-extensions-info",
+ api_levels_module: "api_versions_system",
}
/////////////////////////////////////////////////////////////////////
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index fa4bc0f..7e41660 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -695,6 +695,7 @@
"api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -710,6 +711,7 @@
"system-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -727,6 +729,7 @@
"test-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -742,6 +745,7 @@
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
],
+ enable_validation: false,
}
java_api_library {
@@ -761,6 +765,7 @@
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -774,6 +779,7 @@
"stub-annotations",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
java_api_library {
@@ -798,6 +804,7 @@
visibility: [
"//visibility:private",
],
+ enable_validation: false,
}
java_api_library {
@@ -814,6 +821,7 @@
"android_module_lib_stubs_current.from-text",
],
visibility: ["//visibility:public"],
+ enable_validation: false,
}
////////////////////////////////////////////////////////////////////////
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index c28480e..d9e72b8 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -53,16 +53,6 @@
android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]
android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101]
-android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101]
-android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101]
-android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101]
-android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101]
-android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101]
-android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101]
-android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101]
android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
@@ -83,17 +73,6 @@
android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]
android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101]
-android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101]
-android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101]
-android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101]
-android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101]
-android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101]
-android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101]
-android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101]
-android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101]
android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]
android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101]
@@ -119,11 +98,6 @@
android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]
android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]
-android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101]
-android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101]
-android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101]
-android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
-android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]
android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]
android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]
android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101]
@@ -177,26 +151,6 @@
android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]
android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]
-android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101]
-android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101]
-android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101]
-android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101]
-android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101]
-android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101]
-android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101]
-android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101]
-android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101]
-android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101]
-android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101]
-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/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/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101]
-android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [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/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101]
@@ -218,8 +172,3 @@
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/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131]
-android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131]
-
-java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103]
diff --git a/core/api/current.txt b/core/api/current.txt
index 6c0fccc..f6564ec 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public final class Manifest {
@@ -3288,10 +3286,10 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
- method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
- method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
- method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
- method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl);
+ method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
+ method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl);
+ method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
method public final void disableSelf();
@@ -3403,9 +3401,9 @@
field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3
field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9
field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7
- field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
- field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
- field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2
+ field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0
field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
field public static final String SERVICE_META_DATA = "android.accessibilityservice";
field public static final int SHOW_MODE_AUTO = 0; // 0x0
@@ -5741,6 +5739,7 @@
ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);
method @Deprecated public void onBackStackChanged();
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
method @Deprecated public void setActivity(android.app.Activity);
method @Deprecated public void setMaxVisible(int);
method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener);
@@ -9789,7 +9788,7 @@
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
- method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
+ method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
method @NonNull public android.content.AttributionSource.Builder setPid(int);
}
@@ -13653,6 +13652,7 @@
public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
method public void close();
+ method public String getAttributeNamespace(int);
}
}
@@ -20405,7 +20405,7 @@
method @Deprecated public boolean isPreviewEnabled();
method @Deprecated public boolean isProximityCorrectionEnabled();
method @Deprecated public boolean isShifted();
- method public void onClick(android.view.View);
+ method @Deprecated public void onClick(android.view.View);
method @Deprecated public void onDetachedFromWindow();
method @Deprecated public void onDraw(android.graphics.Canvas);
method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key);
@@ -24232,7 +24232,7 @@
@Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {
method @Deprecated public void apply();
- method public Object clone() throws java.lang.CloneNotSupportedException;
+ method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException;
@@ -25787,15 +25787,15 @@
method public abstract void onDisconnect(android.media.midi.MidiReceiver);
}
- public abstract class MidiUmpDeviceService extends android.app.Service {
+ @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service {
ctor public MidiUmpDeviceService();
- method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
- method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
- method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
- method public void onClose();
- method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
- method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
- field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo();
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose();
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
+ method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
+ field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
}
}
@@ -27096,6 +27096,7 @@
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public String getSelectedTrack(int);
method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
+ method protected void onLayout(boolean, int, int, int, int);
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
method public void reset();
@@ -43549,7 +43550,6 @@
method public int getLongitude();
method public int getNetworkId();
method public int getSystemId();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
}
@@ -43565,7 +43565,6 @@
method @Nullable public String getMncString();
method @Nullable public String getMobileNetworkOperator();
method @Deprecated public int getPsc();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
}
@@ -43583,7 +43582,6 @@
method @Nullable public String getMobileNetworkOperator();
method public int getPci();
method public int getTac();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;
}
@@ -43596,7 +43594,6 @@
method @IntRange(from=0, to=3279165) public int getNrarfcn();
method @IntRange(from=0, to=1007) public int getPci();
method @IntRange(from=0, to=16777215) public int getTac();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
}
@@ -43610,7 +43607,6 @@
method @Nullable public String getMncString();
method @Nullable public String getMobileNetworkOperator();
method public int getUarfcn();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;
}
@@ -43626,7 +43622,6 @@
method @Nullable public String getMobileNetworkOperator();
method public int getPsc();
method public int getUarfcn();
- method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;
}
@@ -43710,6 +43705,7 @@
public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getCdmaDbm();
method public int getCdmaEcio();
@@ -43720,24 +43716,28 @@
method public int getEvdoLevel();
method public int getEvdoSnr();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
}
public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getBitErrorRate();
method public int getDbm();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
method public int getRssi();
method public int getTimingAdvance();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
}
public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method @IntRange(from=0, to=15) public int getCqi();
method @IntRange(from=1, to=6) public int getCqiTableIndex();
@@ -43748,12 +43748,14 @@
method public int getRssi();
method public int getRssnr();
method public int getTimingAdvance();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;
}
public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
@@ -43766,26 +43768,31 @@
method public int getSsRsrq();
method public int getSsSinr();
method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
}
public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getDbm();
method @IntRange(from=0, to=4) public int getLevel();
method public int getRscp();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
}
public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
+ method public boolean equals(Object);
method public int getAsuLevel();
method public int getDbm();
method public int getEcNo();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+ method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
}
@@ -46573,6 +46580,7 @@
method @Deprecated public int length();
method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
method @Deprecated public CharSequence subSequence(int, int);
+ method @Deprecated public String toString();
}
@Deprecated public class AndroidCharacter {
@@ -47024,6 +47032,7 @@
method public void removeSpan(Object);
method public void setSpan(Object, int, int, int);
method public CharSequence subSequence(int, int);
+ method public String toString();
}
public static final class PrecomputedText.Params {
@@ -47153,6 +47162,7 @@
method public void setFilters(android.text.InputFilter[]);
method public void setSpan(Object, int, int, int);
method public CharSequence subSequence(int, int);
+ method public String toString();
method public static android.text.SpannableStringBuilder valueOf(CharSequence);
}
@@ -48793,7 +48803,9 @@
method public boolean containsValue(Object);
method public void ensureCapacity(int);
method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+ method public boolean equals(@Nullable Object);
method public V get(Object);
+ method public int hashCode();
method public int indexOfKey(Object);
method public int indexOfValue(Object);
method public boolean isEmpty();
@@ -48825,7 +48837,9 @@
method public boolean contains(Object);
method public boolean containsAll(java.util.Collection<?>);
method public void ensureCapacity(int);
+ method public boolean equals(@Nullable Object);
method public void forEach(java.util.function.Consumer<? super E>);
+ method public int hashCode();
method public int indexOf(Object);
method public boolean isEmpty();
method public java.util.Iterator<E> iterator();
@@ -57564,6 +57578,7 @@
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);
ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int);
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
}
@Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
@@ -57642,6 +57657,7 @@
method public long getSelectedItemId();
method public int getSelectedItemPosition();
method public abstract android.view.View getSelectedView();
+ method protected void onLayout(boolean, int, int, int, int);
method public boolean performItemClick(android.view.View, int, long);
method public abstract void setAdapter(T);
method public void setEmptyView(android.view.View);
@@ -58288,6 +58304,7 @@
method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();
method public boolean getMeasureAllChildren();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setMeasureAllChildren(boolean);
}
@@ -58341,6 +58358,7 @@
method public boolean getUseDefaultMargins();
method public boolean isColumnOrderPreserved();
method public boolean isRowOrderPreserved();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setAlignmentMode(int);
method public void setColumnCount(int);
method public void setColumnOrderPreserved(boolean);
@@ -58563,6 +58581,7 @@
method public float getWeightSum();
method public boolean isBaselineAligned();
method public boolean isMeasureWithLargestChildEnabled();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setBaselineAligned(boolean);
method public void setBaselineAlignedChildIndex(int);
method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -59111,6 +59130,7 @@
method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
method public int getGravity();
method public int getIgnoreGravity();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setGravity(int);
method public void setHorizontalGravity(int);
method public void setIgnoreGravity(int);
@@ -59565,6 +59585,7 @@
method @Deprecated public boolean isMoving();
method @Deprecated public boolean isOpened();
method @Deprecated public void lock();
+ method @Deprecated protected void onLayout(boolean, int, int, int, int);
method @Deprecated public void open();
method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);
method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener);
@@ -60211,6 +60232,7 @@
method public boolean hideOverflowMenu();
method public void inflateMenu(@MenuRes int);
method public boolean isOverflowMenuShowing();
+ method protected void onLayout(boolean, int, int, int, int);
method public void setCollapseContentDescription(@StringRes int);
method public void setCollapseContentDescription(@Nullable CharSequence);
method public void setCollapseIcon(@DrawableRes int);
@@ -60365,7 +60387,7 @@
method @Deprecated public android.view.View getZoomControls();
method @Deprecated public boolean isAutoDismissed();
method @Deprecated public boolean isVisible();
- method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);
method @Deprecated public void setAutoDismissed(boolean);
method @Deprecated public void setFocusable(boolean);
method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 500a12c..b5d3ed7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/core/api/module-lib-removed.txt
+++ b/core/api/module-lib-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/api/removed.txt b/core/api/removed.txt
index e2b4e4d..5a4be65 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.app {
public class Notification implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1f20cb9..c72d09d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -3557,7 +3555,7 @@
field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
- field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+ field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -4030,7 +4028,7 @@
field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
- field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
+ field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L
field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -4061,7 +4059,9 @@
}
public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+ method public int describeContents();
method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
}
@@ -5961,6 +5961,7 @@
public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+ method public int describeContents();
method public int getId();
method @NonNull public java.util.Locale getLocale();
method public int getRecognitionModes();
@@ -5983,6 +5984,7 @@
public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+ method public int describeContents();
method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();
method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5990,6 +5992,7 @@
}
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+ method public int describeContents();
method public int getEnd();
method public int getStart();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -6717,7 +6720,7 @@
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
method public String toLogFriendlyString();
- method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
+ method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);
field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0
field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1
@@ -6800,6 +6803,7 @@
public abstract class MusicRecognitionService extends android.app.Service {
ctor public MusicRecognitionService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
}
@@ -6858,6 +6862,7 @@
public abstract class SoundTriggerDetectionService extends android.app.Service {
ctor public SoundTriggerDetectionService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int);
@@ -7571,6 +7576,7 @@
}
public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method protected void finalize();
method public long getAudioHandle();
method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public long getAvDataId();
@@ -8965,6 +8971,8 @@
package android.net.metrics {
@Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class ApfProgramEvent.Builder {
@@ -8979,6 +8987,8 @@
}
@Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class ApfStats.Builder {
@@ -8997,6 +9007,8 @@
}
@Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class DhcpClientEvent.Builder {
@@ -9008,7 +9020,9 @@
@Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public DhcpErrorEvent(int);
+ method @Deprecated public int describeContents();
method @Deprecated public static int errorCodeWithOption(int, int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
@@ -9046,6 +9060,8 @@
@Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public IpManagerEvent(int, long);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
@@ -9058,6 +9074,8 @@
@Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public IpReachabilityEvent(int);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int NUD_FAILED = 512; // 0x200
field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
field @Deprecated public static final int PROBE = 256; // 0x100
@@ -9068,6 +9086,8 @@
@Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
ctor @Deprecated public NetworkEvent(int, long);
ctor @Deprecated public NetworkEvent(int);
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
@@ -9084,6 +9104,8 @@
}
@Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
}
@Deprecated public static final class RaEvent.Builder {
@@ -9098,7 +9120,9 @@
}
@Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+ method @Deprecated public int describeContents();
method @Deprecated @NonNull public static String getProbeName(int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
field @Deprecated public static final int PROBE_DNS = 0; // 0x0
@@ -11503,6 +11527,7 @@
public abstract class FieldClassificationService extends android.app.Service {
ctor public FieldClassificationService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
method public void onConnected();
method public void onDisconnected();
@@ -11581,6 +11606,7 @@
method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -11619,6 +11645,7 @@
public final class FillWindow implements java.lang.AutoCloseable {
ctor public FillWindow();
+ method public void close();
method public void destroy();
method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
}
@@ -11644,6 +11671,7 @@
public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {
ctor public CarrierMessagingServiceWrapper();
method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
+ method public void close();
method public void disconnect();
method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
@@ -11694,6 +11722,7 @@
method public final void disableSelf();
method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);
method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
+ method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
@@ -11732,6 +11761,7 @@
public abstract class ContentSuggestionsService extends android.app.Service {
ctor public ContentSuggestionsService();
+ method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);
method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle);
@@ -11745,6 +11775,7 @@
public abstract class DataLoaderService extends android.app.Service {
ctor public DataLoaderService();
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
}
@@ -12417,6 +12448,7 @@
public class TraceReportService extends android.app.Service {
ctor public TraceReportService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
}
@@ -13042,6 +13074,7 @@
method @Nullable public android.content.ComponentName getCallScreeningComponent();
method public boolean isBlocked();
method public boolean isInContacts();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;
}
@@ -13351,6 +13384,7 @@
method public int getReason();
method public int getTimeoutSeconds();
method public boolean isEnabled();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
field public static final int REASON_ALL = 4; // 0x4
field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
@@ -13635,6 +13669,7 @@
method public int getDownlinkCapacityKbps();
method public int getType();
method public int getUplinkCapacityKbps();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
field public static final int INVALID = -1; // 0xffffffff
field public static final int LCE_TYPE_COMBINED = 2; // 0x2
@@ -13644,8 +13679,10 @@
public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public LteVopsSupportInfo(int, int);
+ method public boolean equals(@Nullable Object);
method public int getEmcBearerSupport();
method public int getVopsSupport();
+ method public int hashCode();
method public boolean isEmergencyServiceFallbackSupported();
method public boolean isEmergencyServiceSupported();
method public boolean isVopsSupported();
@@ -13746,9 +13783,11 @@
public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
ctor public NrVopsSupportInfo(int, int, int);
+ method public boolean equals(@Nullable Object);
method public int getEmcSupport();
method public int getEmfSupport();
method public int getVopsSupport();
+ method public int hashCode();
method public boolean isEmergencyServiceFallbackSupported();
method public boolean isEmergencyServiceSupported();
method public boolean isVopsSupported();
@@ -14861,6 +14900,7 @@
public abstract class QualifiedNetworksService extends android.app.Service {
ctor public QualifiedNetworksService();
+ method public android.os.IBinder onBind(android.content.Intent);
method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
}
@@ -15052,6 +15092,7 @@
public class GbaService extends android.app.Service {
ctor public GbaService();
method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean);
+ method public android.os.IBinder onBind(android.content.Intent);
method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;
method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;
field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService";
@@ -15554,6 +15595,7 @@
method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);
method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
+ method public android.os.IBinder onBind(android.content.Intent);
method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;
method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();
method public void readyForFeatureCreation();
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 1fa2718..aa17df3 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.app {
public class AppOpsManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index eeddeb2..b905287 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android {
public static final class Manifest.permission {
@@ -3187,7 +3185,6 @@
field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
field public static final int HAL_SERVICE_MODEM = 3; // 0x3
field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
- field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
field public static final int HAL_SERVICE_SIM = 5; // 0x5
field public static final int HAL_SERVICE_VOICE = 6; // 0x6
field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3587,8 +3584,8 @@
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
- field public float preferredMaxDisplayRefreshRate;
- field public float preferredMinDisplayRefreshRate;
+ field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate;
+ field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
@@ -3618,7 +3615,6 @@
public final class AccessibilityWindowInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
- field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff
}
}
@@ -3946,7 +3942,9 @@
package android.window {
public final class BackNavigationInfo implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public static String typeToString(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
field public static final String KEY_TRIGGER_BACK = "TriggerBack";
field public static final int TYPE_CALLBACK = 4; // 0x4
@@ -4007,11 +4005,13 @@
}
public final class TaskFragmentCreationParams implements android.os.Parcelable {
+ method public int describeContents();
method @NonNull public android.os.IBinder getFragmentToken();
method @NonNull public android.graphics.Rect getInitialRelativeBounds();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
method @NonNull public android.os.IBinder getOwnerToken();
method public int getWindowingMode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
}
@@ -4023,6 +4023,7 @@
}
public final class TaskFragmentInfo implements android.os.Parcelable {
+ method public int describeContents();
method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
method @NonNull public java.util.List<android.os.IBinder> getActivities();
method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment();
@@ -4036,6 +4037,7 @@
method public boolean isEmpty();
method public boolean isTaskClearedForReuse();
method public boolean isVisible();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
}
@@ -4058,6 +4060,8 @@
}
public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
}
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index 14191eb..d802177 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3370c12..1000612 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -23,6 +23,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -793,6 +794,7 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
@IntDef(
prefix = {"OVERLAY_RESULT_"},
value = {
@@ -803,6 +805,7 @@
public @interface AttachOverlayResult {}
/** Result code indicating the overlay was successfully attached. */
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_SUCCESS = 0;
/**
@@ -810,6 +813,7 @@
* error and not
* because of problems with the input.
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;
/**
@@ -817,6 +821,7 @@
* specified display or
* window id was invalid.
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public static final int OVERLAY_RESULT_INVALID = 2;
private int mConnectionId = AccessibilityInteractionClient.NO_ID;
@@ -3506,11 +3511,7 @@
* @param displayId the display to which the SurfaceControl should be attached.
* @param sc the SurfaceControl containing the overlay content
*
- * @deprecated Use
- * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)}
- * instead.
*/
- @Deprecated
public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {
Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
AccessibilityInteractionClient.getInstance(this)
@@ -3547,6 +3548,7 @@
* @see #OVERLAY_RESULT_INVALID
* @see #OVERLAY_RESULT_INTERNAL_ERROR
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public void attachAccessibilityOverlayToDisplay(
int displayId,
@NonNull SurfaceControl sc,
@@ -3581,11 +3583,7 @@
* @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.
* @param sc the SurfaceControl containing the overlay content
*
- * @deprecated Use
- * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)}
- * instead.
*/
- @Deprecated
public void attachAccessibilityOverlayToWindow(
int accessibilityWindowId, @NonNull SurfaceControl sc) {
Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");
@@ -3623,6 +3621,7 @@
* @see #OVERLAY_RESULT_INVALID
* @see #OVERLAY_RESULT_INTERNAL_ERROR
*/
+ @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")
public void attachAccessibilityOverlayToWindow(
int accessibilityWindowId,
@NonNull SurfaceControl sc,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 895dde7..f2c0051 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2073,9 +2073,7 @@
* Allow a {@link PendingIntent} to use the privilege of its creator to start background
* activities.
*
- * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set
- * @throws IllegalArgumentException is the value is not a valid
- * {@link android.app.ComponentOptions.BackgroundActivityStartMode}
+ * @param mode the mode being set
*/
@NonNull
public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode(
@@ -2088,7 +2086,7 @@
* Returns the mode to start background activities granted by the creator of the
* {@link PendingIntent}.
*
- * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set
+ * @return the mode currently set
*/
public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {
return mPendingIntentCreatorBackgroundActivityStartMode;
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9549ebf..41b4004 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -843,8 +843,7 @@
* considered to be in the same delivery group as this iff it has the same {@code namespace}
* and {@code key}.
*
- * <p> If neither matching key using this API nor matching filter using
- * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
+ * <p> If not matching key using this API then by default
* {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
*/
@NonNull
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8fea03b..ebf183f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -50,7 +50,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.security.net.config.NetworkSecurityConfigProvider;
-import android.sysprop.VndkProperties;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -901,14 +900,10 @@
}
// Similar to vendor apks, we should add /product/lib for apks from product partition
- // when product apps are marked as unbundled. We cannot use the same way from vendor
- // to check if lib path exists because there is possibility that /product/lib would not
- // exist from legacy device while product apks are bundled. To make this clear, we use
- // "ro.product.vndk.version" property. If the property is defined, we regard all product
- // apks as unbundled.
+ // when product apps are marked as unbundled. Product is separated as long as the
+ // partition exists, so it can be handled with same approach from the vendor partition.
if (mApplicationInfo.getCodePath() != null
- && mApplicationInfo.isProduct()
- && VndkProperties.product_vndk_version().isPresent()) {
+ && mApplicationInfo.isProduct()) {
isBundledApp = false;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dd7db23..94e1292 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1514,13 +1514,13 @@
/**
* {@link #extras} key: the color used as a hint for the Answer action button of a
- * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
*/
public static final String EXTRA_ANSWER_COLOR = "android.answerColor";
/**
* {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a
- * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}.
+ * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.
*/
public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
@@ -1704,7 +1704,7 @@
private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
/**
- * {@link }: No semantic action defined.
+ * No semantic action defined.
*/
public static final int SEMANTIC_ACTION_NONE = 0;
@@ -7923,7 +7923,7 @@
* (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated
* conversation section in the shade above non-conversation alerting and silence notifications.
* To be a valid conversation shortcut, the shortcut must be a
- * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut.
+ * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.
*
* <p>
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 15bd1dc..e268968 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -651,6 +651,7 @@
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
AutofillOverlay mAutofillOverlay;
+ boolean mIsCredential;
int mX;
int mY;
@@ -799,6 +800,7 @@
if (autofillFlags != 0) {
mSanitized = in.readInt() == 1;
+ mIsCredential = in.readInt() == 1;
mImportantForAutofill = in.readInt();
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1033,6 +1035,7 @@
if (autofillFlags != 0) {
out.writeInt(mSanitized ? 1 : 0);
+ out.writeInt(mIsCredential ? 1 : 0);
out.writeInt(mImportantForAutofill);
writeSensitive = mSanitized || !sanitizeOnWrite;
if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) {
@@ -1246,6 +1249,19 @@
}
/**
+ * @return whether the node is a credential.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ * TODO(b/303677885): add TestApi
+ *
+ * @hide
+ */
+ public boolean isCredential() {
+ return mIsCredential;
+ }
+
+ /**
* Gets the {@link android.text.InputType} bits of this structure.
*
* @return bits as defined by {@link android.text.InputType}.
@@ -2183,6 +2199,11 @@
}
@Override
+ public void setIsCredential(boolean isCredential) {
+ mNode.mIsCredential = isCredential;
+ }
+
+ @Override
public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) {
mNode.mReceiveContentMimeTypes = mimeTypes;
}
@@ -2498,7 +2519,9 @@
+ ", value=" + node.getAutofillValue()
+ ", sanitized=" + node.isSanitized()
+ ", important=" + node.getImportantForAutofill()
- + ", visibility=" + node.getVisibility());
+ + ", visibility=" + node.getVisibility()
+ + ", isCredential=" + node.isCredential()
+ );
}
final int NCHILDREN = node.getChildCount();
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bfc1eec..62fbcaf 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -730,7 +730,7 @@
/**
* The next app to receive the permission protected data.
*
- * @deprecated Use {@link setNextAttributionSource} instead.
+ * @deprecated Use {@link #setNextAttributionSource} instead.
*/
@Deprecated
public @NonNull Builder setNext(@Nullable AttributionSource value) {
@@ -744,6 +744,7 @@
/**
* The next app to receive the permission protected data.
*/
+ @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)
public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {
checkNotUsed();
mBuilderFieldsSet |= 0x20;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b6a98a5..59bb73b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -323,7 +323,7 @@
// Make sure no flag uses the sign bit (most significant bit) of the long integer,
// to avoid future confusion.
BIND_BYPASS_USER_NETWORK_RESTRICTIONS,
- BIND_FILTER_OUT_QUARANTINED_COMPONENTS,
+ BIND_MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlagsLongBits {}
@@ -703,7 +703,7 @@
*
* @hide
*/
- public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
+ public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L;
/**
@@ -4357,7 +4357,6 @@
* @see android.telephony.CarrierConfigManager
* @see #EUICC_SERVICE
* @see android.telephony.euicc.EuiccManager
- * @see android.telephony.MmsManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5f4c05f..b765562 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3929,6 +3929,8 @@
* {@link #ACTION_BOOT_COMPLETED} is sent. This is sent as a foreground
* broadcast, since it is part of a visible user interaction; be as quick
* as possible when handling it.
+ *
+ * <p><b>Note:</b> This broadcast is not sent to the system user.
*/
public static final String ACTION_USER_INITIALIZE =
"android.intent.action.USER_INITIALIZE";
@@ -4180,7 +4182,7 @@
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_AVAILABLE =
@@ -4194,7 +4196,7 @@
* new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
- * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * a generic broadcast for all profile users.
*/
@FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
public static final String ACTION_PROFILE_UNAVAILABLE =
@@ -4222,7 +4224,7 @@
* that was removed.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
* It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a
* managed user is removed.
*
@@ -4242,7 +4244,7 @@
* that was added.
*
* <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a
- * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}.
+ * generic broadcast for all profile users.
* It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a
* managed user is added.
*
@@ -5323,6 +5325,7 @@
* @hide
*/
@SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
// ---------------------------------------------------------------------
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4d5d056..1b60f8e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -838,7 +838,7 @@
GET_DISABLED_COMPONENTS,
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
- FILTER_OUT_QUARANTINED_COMPONENTS,
+ MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComponentInfoFlagsBits {}
@@ -863,7 +863,7 @@
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
MATCH_CLONE_PROFILE,
- FILTER_OUT_QUARANTINED_COMPONENTS,
+ MATCH_QUARANTINED_COMPONENTS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResolveInfoFlagsBits {}
@@ -1252,12 +1252,13 @@
*/
// TODO(b/278553670) Unhide and update @links before launch.
@SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
/**
* @hide
*/
- public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L;
+ public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
/**
* Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b2cc070..db12728 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -50,3 +50,11 @@
description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
bug: "304741685"
}
+
+flag {
+ name: "sdk_lib_independence"
+ namespace: "package_manager_service"
+ description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable."
+ bug: "295827951"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 8503072..38fbd72 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -128,6 +128,11 @@
@TestApi
@FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)
public CharSequence getSettingsActivity() {
+ // Add a manual check to make sure this returns null if
+ // the flag is not enabled.
+ if (!Flags.settingsActivityEnabled()) {
+ return null;
+ }
return mSettingsActivity;
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a4593be..aeddd0c 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -31,6 +31,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -775,7 +776,8 @@
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
@Nullable Handler handler, @EventsMask long eventsMask) {
- mGlobal.registerDisplayListener(listener, handler, eventsMask);
+ mGlobal.registerDisplayListener(listener, handler, eventsMask,
+ ActivityThread.currentPackageName());
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 6d6085b..8decd50 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -45,8 +46,11 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
+import android.sysprop.DisplayProperties;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -72,7 +76,13 @@
*/
public final class DisplayManagerGlobal {
private static final String TAG = "DisplayManager";
- private static final boolean DEBUG = false;
+
+ private static final String EXTRA_LOGGING_PACKAGE_NAME =
+ DisplayProperties.debug_vri_package().orElse(null);
+ private static String sCurrentPackageName = ActivityThread.currentPackageName();
+ private static boolean sExtraDisplayListenerLogging = initExtraLogging();
+
+ private static final boolean DEBUG = false || sExtraDisplayListenerLogging;
// True if display info and display ids should be cached.
//
@@ -130,6 +140,8 @@
@VisibleForTesting
public DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
+ initExtraLogging();
+
try {
mWideColorSpace =
ColorSpace.get(
@@ -208,7 +220,7 @@
registerCallbackIfNeededLocked();
- if (DEBUG) {
+ if (DEBUG || extraLogging()) {
Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
}
return info;
@@ -321,12 +333,14 @@
* If null, listener will use the handler for the current thread, and if still null,
* the handler for the main thread.
* If that is still null, a runtime exception will be thrown.
+ * @param packageName of the calling package.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
- @Nullable Handler handler, @EventsMask long eventsMask) {
+ @Nullable Handler handler, @EventsMask long eventsMask, String packageName) {
Looper looper = getLooperForHandler(handler);
Handler springBoard = new Handler(looper);
- registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask);
+ registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask,
+ packageName);
}
/**
@@ -334,9 +348,11 @@
*
* @param listener The listener that will be called when display changes occur.
* @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
+ * @param eventsMask Mask of events to be listened to.
+ * @param packageName of the calling package.
*/
public void registerDisplayListener(@NonNull DisplayListener listener,
- @NonNull Executor executor, @EventsMask long eventsMask) {
+ @NonNull Executor executor, @EventsMask long eventsMask, String packageName) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
@@ -345,15 +361,22 @@
throw new IllegalArgumentException("The set of events to listen to must not be empty.");
}
+ if (extraLogging()) {
+ Slog.i(TAG, "Registering Display Listener: "
+ + Long.toBinaryString(eventsMask) + ", packageName: " + packageName);
+ }
+
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index < 0) {
- mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask));
+ mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask,
+ packageName));
registerCallbackIfNeededLocked();
} else {
mDisplayListeners.get(index).setEventsMask(eventsMask);
}
updateCallbackIfNeededLocked();
+ maybeLogAllDisplayListeners();
}
}
@@ -362,6 +385,10 @@
throw new IllegalArgumentException("listener must not be null");
}
+ if (extraLogging()) {
+ Slog.i(TAG, "Unregistering Display Listener: " + listener);
+ }
+
synchronized (mLock) {
int index = findDisplayListenerLocked(listener);
if (index >= 0) {
@@ -371,6 +398,18 @@
updateCallbackIfNeededLocked();
}
}
+ maybeLogAllDisplayListeners();
+ }
+
+ private void maybeLogAllDisplayListeners() {
+ if (!sExtraDisplayListenerLogging) {
+ return;
+ }
+
+ Slog.i(TAG, "Currently Registered Display Listeners:");
+ for (int i = 0; i < mDisplayListeners.size(); i++) {
+ Slog.i(TAG, i + ": " + mDisplayListeners.get(i));
+ }
}
private static Looper getLooperForHandler(@Nullable Handler handler) {
@@ -1148,15 +1187,20 @@
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Executor mExecutor;
private AtomicLong mGenerationId = new AtomicLong(1);
+ private final String mPackageName;
DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
- @EventsMask long eventsMask) {
+ @EventsMask long eventsMask, String packageName) {
mExecutor = executor;
mListener = listener;
mEventsMask = eventsMask;
+ mPackageName = packageName;
}
public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending Display Event: " + eventToString(event));
+ }
long generationId = mGenerationId.get();
Message msg = Message.obtain(null, event, displayId, 0, info);
mExecutor.execute(() -> {
@@ -1177,6 +1221,14 @@
}
private void handleMessage(Message msg) {
+ if (extraLogging()) {
+ Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what)
+ + ", display=" + msg.arg1
+ + ", mEventsMask=" + Long.toBinaryString(mEventsMask)
+ + ", mPackageName=" + mPackageName
+ + ", msg.obj=" + msg.obj
+ + ", listener=" + mListener.getClass() + ")");
+ }
if (DEBUG) {
Trace.beginSection(
"DisplayListenerDelegate(" + eventToString(msg.what)
@@ -1193,6 +1245,10 @@
if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
DisplayInfo newInfo = (DisplayInfo) msg.obj;
if (newInfo != null && !newInfo.equals(mDisplayInfo)) {
+ if (extraLogging()) {
+ Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
+ + newInfo);
+ }
mDisplayInfo.copyFrom(newInfo);
mListener.onDisplayChanged(msg.arg1);
}
@@ -1228,6 +1284,11 @@
Trace.endSection();
}
}
+
+ @Override
+ public String toString() {
+ return "mask: {" + mEventsMask + "}, for " + mListener.getClass();
+ }
}
/**
@@ -1353,4 +1414,19 @@
}
return "UNKNOWN";
}
+
+
+ private static boolean initExtraLogging() {
+ if (sCurrentPackageName == null) {
+ sCurrentPackageName = ActivityThread.currentPackageName();
+ sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
+ && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
+ }
+ return sExtraDisplayListenerLogging;
+ }
+
+ private static boolean extraLogging() {
+ return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals(
+ sCurrentPackageName);
+ }
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ff1a6ac..7b8419e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1373,7 +1373,7 @@
public interface InputDeviceListener {
/**
* Called whenever an input device has been added to the system.
- * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device.
+ * Use {@link #getInputDevice(int)} to get more information about the device.
*
* @param deviceId The id of the input device that was added.
*/
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 582c5c0..4c2bbc1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3231,7 +3231,7 @@
}
/**
- * Called when the user tapped or clicked an {@link android.widget.Editor}.
+ * Called when the user tapped or clicked an editor.
* This can be useful when IME makes a decision of showing Virtual keyboard based on what
* {@link MotionEvent#getToolType(int)} was used to click the editor.
* e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8482945..e85b7bf 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1854,7 +1854,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public HistoryItem next;
- // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
+ // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().
@UnsupportedAppUsage
public long time;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 49d7e8b..32840d4 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -300,6 +300,7 @@
* @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()
* @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()
*/
+ // TODO(b/298459065): switch to monotonic clock
public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {
mFromTimestamp = fromTimestamp;
mToTimestamp = toTimestamp;
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 086b0e5..58def6e 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -143,10 +143,11 @@
*/
public void onError(@BugreportErrorCode int errorCode) {}
- /** Called when taking bugreport finishes successfully.
+ /**
+ * Called when taking bugreport finishes successfully.
*
* <p>This callback will be invoked if the
- * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
+ * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
*/
public void onFinished() {}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4174c1c..fce715a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2670,7 +2670,7 @@
/**
* Called when overall thermal throttling status changed.
- * @param status defined in {@link android.os.Temperature}.
+ * @param status the status
*/
void onThermalStatusChanged(@ThermalStatus int status);
}
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 970f419..ace5f7e 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -66,7 +66,7 @@
/**
* Rethrow this exception when we know it came from the system server. This
* gives us an opportunity to throw a nice clean
- * {@link DeadSystemRuntimeException} signal to avoid spamming logs with
+ * {@code DeadSystemRuntimeException} signal to avoid spamming logs with
* misleading stack traces.
* <p>
* Apps making calls into the system server may end up persisting internal
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 37559b3..7fceda4 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@
flag {
name: "remove_app_profiler_pss_collection"
- namespace: "android_platform_power_optimization"
+ namespace: "power_optimization"
description: "Replaces background PSS collection in AppProfiler with RSS"
bug: "297542292"
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d8534dd..d60d4c6 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -21,3 +21,10 @@
description: "enable role controller in system server"
bug: "302562590"
}
+
+flag {
+ name: "set_next_attribution_source"
+ namespace: "permissions"
+ description: "enable AttributionSource.setNextAttributionSource"
+ bug: "304478648"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b19a034..b34e09f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1800,7 +1800,6 @@
* Input: Nothing.
* <p>
* Output: Nothing.
- * @see android.service.notification.NotificationAssistantService
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS =
@@ -2507,8 +2506,8 @@
* to the caller package.
* <p>
* <b>NOTE: </b> Applications should call
- * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()}
- * and only use this action to start an activity if they return {@code false}.
+ * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService(
+ * ComponentName)} and only use this action to start an activity if they return {@code false}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREDENTIAL_PROVIDER =
@@ -12375,7 +12374,7 @@
* Value to specify if the device's UTC system clock should be set automatically, e.g. using
* telephony signals like NITZ, or other sources like GNSS or NTP.
*
- * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+ * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
* automatic time detection instead of directly observing this setting as it may be ignored
* by the time_detector service under various conditions.
*
@@ -12388,7 +12387,7 @@
* Value to specify if the device's time zone system property should be set automatically,
* e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location.
*
- * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of
+ * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of
* automatic time zone detection instead of directly observing this setting as it may be
* ignored by the time_zone_detector service under various conditions.
*
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 3fb94ee..7ea74d3 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -406,7 +406,7 @@
*
* Call this when a field has been detected with a type.
*
- * Altough similiarly named with {@link setFieldClassificationIds},
+ * Altough similiarly named with {@link #setFieldClassificationIds},
* it provides a different functionality - setFieldClassificationIds should
* be used when a field is only suspected to be Autofillable.
* This method should be used when a field is certainly Autofillable
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 954cc96..53706cd3 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -813,7 +813,7 @@
* If no {@link #Builder(int, AutofillId[]) required ids},
* or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}
* were set, Save Dialog will only be triggered if platform detection is enabled, which
- * is indicated when {@link FillRequest.getHints()} is not empty.
+ * is indicated when {@link FillRequest#getHints()} is not empty.
*/
public SaveInfo build() {
throwIfDestroyed();
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 1cfff14..759953e 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -896,8 +896,7 @@
* <p>This method will throw a security exception if you don't have access to notifications
* for the given user.</p>
* <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
- * device} or be the {@link NotificationAssistantService notification assistant} in order to
- * use this method.
+ * device} or be the notification assistant in order to use this method.
*
* @param pkg The package to retrieve channels for.
*/
@@ -920,8 +919,7 @@
* <p>This method will throw a security exception if you don't have access to notifications
* for the given user.</p>
* <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated
- * device} or be the {@link NotificationAssistantService notification assistant} in order to
- * use this method.
+ * device} or be the notification assistant in order to use this method.
*
* @param pkg The package to retrieve channel groups for.
*/
@@ -2001,7 +1999,7 @@
/**
* Returns a list of smart {@link Notification.Action} that can be added by the
- * {@link NotificationAssistantService}
+ * notification assistant.
*/
public @NonNull List<Notification.Action> getSmartActions() {
return mSmartActions == null ? Collections.emptyList() : mSmartActions;
@@ -2022,8 +2020,7 @@
}
/**
- * Returns a list of smart replies that can be added by the
- * {@link NotificationAssistantService}
+ * Returns a list of smart replies that can be added by the notification assistant.
*/
public @NonNull List<CharSequence> getSmartReplies() {
return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index 4a4fd04..bf129c5 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -372,9 +372,9 @@
}
/**
- * Set of locations this card might be useful at. If {@link
- * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
- * shown to the user when a user is near one of these locations.
+ * Set of locations this card might be useful at. If
+ * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is
+ * enabled, the card might be shown to the user when a user is near one of these locations.
*/
@NonNull
public Builder setCardLocations(@NonNull List<Location> cardLocations) {
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index cabcae3..d40b39e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1809,7 +1809,7 @@
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
* referring to the device that sent the request. Starting from Android 14, the system will
- * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
+ * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
* application should handle null case before Android 14.
* @param showFlags The show flags originally provided to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 8862f1d..a0cd074 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -1274,7 +1274,7 @@
}
/**
- * Gets the {@link LineBreakconfig} used in this DynamicLayout.
+ * Gets the {@link LineBreakConfig} used in this DynamicLayout.
* Use this only to consult the LineBreakConfig's properties and not
* to change them.
*
diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java
index be002f3..b0a70ea 100644
--- a/core/java/android/text/WordSegmentFinder.java
+++ b/core/java/android/text/WordSegmentFinder.java
@@ -24,7 +24,7 @@
/**
* Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are
- * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in
+ * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in
* any text segments.
*
* <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello",
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0b2b6ce..5069455 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.KeyguardManager;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1366,7 +1367,8 @@
// form of the larger DISPLAY_CHANGED event
mGlobal.registerDisplayListener(toRegister, executor,
DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
- | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED,
+ ActivityThread.currentPackageName());
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 16318e0..e9d0e4c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9308,6 +9308,7 @@
structure.setAutofillType(autofillType);
structure.setAutofillHints(getAutofillHints());
structure.setAutofillValue(getAutofillValue());
+ structure.setIsCredential(isCredential());
}
structure.setImportantForAutofill(getImportantForAutofill());
structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes());
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b5648cc..9392783 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1549,7 +1549,8 @@
mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED
| DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+ mBasePackageName);
}
/**
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 2c2ae06..bb2c7c8 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -397,6 +397,13 @@
public void setImportantForAutofill(@AutofillImportance int mode) {}
/**
+ * Sets whether the node is a credential. See {@link View#isCredential}.
+ *
+ * @hide
+ */
+ public void setIsCredential(boolean isCredential) {}
+
+ /**
* Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}.
*
* <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a3b93b4..4f03ce9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,6 +17,7 @@
package android.view;
import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST;
import static android.view.View.STATUS_BAR_DISABLE_BACK;
import static android.view.View.STATUS_BAR_DISABLE_CLOCK;
import static android.view.View.STATUS_BAR_DISABLE_EXPAND;
@@ -3905,6 +3906,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
@TestApi
public float preferredMinDisplayRefreshRate;
@@ -3914,6 +3916,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)
@TestApi
public float preferredMaxDisplayRefreshRate;
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 60f46e6..12ce0f4 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -1731,6 +1731,9 @@
@Override
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId) {
+ if (!Flags.a11yOverlayCallbacks()) {
+ return;
+ }
synchronized (mInstanceLock) {
if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {
final Pair<Executor, IntConsumer> pair =
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 09b2f9c..fa0052c 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -99,7 +99,6 @@
/** @hide */
public static final int UNDEFINED_CONNECTION_ID = -1;
/** @hide */
- @TestApi
public static final int UNDEFINED_WINDOW_ID = -1;
/** @hide */
public static final int ANY_WINDOW_ID = -2;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 85dadd4..cc612ed 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -13,3 +13,10 @@
description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"
bug: "303871725"
}
+
+flag {
+ name: "a11y_overlay_callbacks"
+ namespace: "accessibility"
+ description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+ bug: "304478691"
+}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2241fd5..b44d6a4 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -317,16 +317,14 @@
}
}
- // Should not be possible for mComponentName to be null here but check anyway
- if (mManager.mOptions.contentProtectionOptions.enableReceiver
- && mManager.getContentProtectionEventBuffer() != null
- && mComponentName != null) {
+ if (isContentProtectionEnabled()) {
mContentProtectionEventProcessor =
new ContentProtectionEventProcessor(
mManager.getContentProtectionEventBuffer(),
mHandler,
mSystemServerInterface,
- mComponentName.getPackageName());
+ mComponentName.getPackageName(),
+ mManager.mOptions.contentProtectionOptions);
} else {
mContentProtectionEventProcessor = null;
}
@@ -956,4 +954,15 @@
private boolean isContentCaptureReceiverEnabled() {
return mManager.mOptions.enableReceiver;
}
+
+ @UiThread
+ private boolean isContentProtectionEnabled() {
+ // Should not be possible for mComponentName to be null here but check anyway
+ // Should not be possible for groups to be empty if receiver is enabled but check anyway
+ return mManager.mOptions.contentProtectionOptions.enableReceiver
+ && mManager.getContentProtectionEventBuffer() != null
+ && mComponentName != null
+ && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty()
+ || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty());
+ }
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
index b44abf3..aaf90bd 100644
--- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -19,9 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.content.ContentCaptureOptions;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
-import android.text.InputType;
import android.util.Log;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.IContentCaptureManager;
@@ -33,10 +33,10 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.stream.Stream;
/**
* Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
@@ -47,33 +47,13 @@
private static final String TAG = "ContentProtectionEventProcessor";
- private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
- Collections.unmodifiableList(
- Arrays.asList(
- InputType.TYPE_NUMBER_VARIATION_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
-
- private static final List<String> PASSWORD_TEXTS =
- Collections.unmodifiableList(
- Arrays.asList("password", "pass word", "code", "pin", "credential"));
-
- private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
- Collections.unmodifiableList(
- Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
-
private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
- private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
-
private static final Set<Integer> EVENT_TYPES_TO_STORE =
- Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(
- ContentCaptureEvent.TYPE_VIEW_APPEARED,
- ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
- ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+ Set.of(
+ ContentCaptureEvent.TYPE_VIEW_APPEARED,
+ ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED);
private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
@@ -85,11 +65,7 @@
@NonNull private final String mPackageName;
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- public boolean mPasswordFieldDetected = false;
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- public boolean mSuspiciousTextDetected = false;
+ @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
@@ -97,15 +73,32 @@
private int mResetLoginRemainingEventsToProcess;
+ private boolean mAnyGroupFound = false;
+
+ // Ordered by priority
+ private final List<SearchGroup> mGroupsRequired;
+
+ // Ordered by priority
+ private final List<SearchGroup> mGroupsOptional;
+
+ // Ordered by priority
+ private final List<SearchGroup> mGroupsAll;
+
public ContentProtectionEventProcessor(
@NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
@NonNull Handler handler,
@NonNull IContentCaptureManager contentCaptureManager,
- @NonNull String packageName) {
+ @NonNull String packageName,
+ @NonNull ContentCaptureOptions.ContentProtectionOptions options) {
mEventBuffer = eventBuffer;
mHandler = handler;
mContentCaptureManager = contentCaptureManager;
mPackageName = packageName;
+ mOptions = options;
+ mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList();
+ mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList();
+ mGroupsAll =
+ Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList();
}
/** Main entry point for {@link ContentCaptureEvent} processing. */
@@ -130,9 +123,31 @@
@UiThread
private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
- mPasswordFieldDetected |= isPasswordField(event);
- mSuspiciousTextDetected |= isSuspiciousText(event);
- if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+ ViewNode viewNode = event.getViewNode();
+ String eventText = ContentProtectionUtils.getEventTextLower(event);
+ String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode);
+ String hintText = ContentProtectionUtils.getHintTextLower(viewNode);
+
+ mGroupsAll.stream()
+ .filter(group -> !group.mFound)
+ .filter(
+ group ->
+ group.matches(eventText)
+ || group.matches(viewNodeText)
+ || group.matches(hintText))
+ .findFirst()
+ .ifPresent(
+ group -> {
+ group.mFound = true;
+ mAnyGroupFound = true;
+ });
+
+ boolean loginDetected =
+ mGroupsRequired.stream().allMatch(group -> group.mFound)
+ && mGroupsOptional.stream().filter(group -> group.mFound).count()
+ >= mOptions.optionalGroupsThreshold;
+
+ if (loginDetected) {
loginDetected();
} else {
maybeResetLoginFlags();
@@ -150,14 +165,13 @@
@UiThread
private void resetLoginFlags() {
- mPasswordFieldDetected = false;
- mSuspiciousTextDetected = false;
- mResetLoginRemainingEventsToProcess = 0;
+ mGroupsAll.forEach(group -> group.mFound = false);
+ mAnyGroupFound = false;
}
@UiThread
private void maybeResetLoginFlags() {
- if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+ if (mAnyGroupFound) {
if (mResetLoginRemainingEventsToProcess <= 0) {
mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
} else {
@@ -194,61 +208,21 @@
}
}
- private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
- return isPasswordField(event.getViewNode());
- }
+ private static final class SearchGroup {
- private boolean isPasswordField(@Nullable ViewNode viewNode) {
- if (viewNode == null) {
- return false;
+ @NonNull private final List<String> mSearchStrings;
+
+ public boolean mFound = false;
+
+ SearchGroup(@NonNull List<String> searchStrings) {
+ mSearchStrings = searchStrings;
}
- return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
- }
- private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
- if (!isAndroidViewNode(viewNode)) {
- return false;
+ public boolean matches(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ return mSearchStrings.stream().anyMatch(text::contains);
}
- int inputType = viewNode.getInputType();
- return PASSWORD_FIELD_INPUT_TYPES.stream()
- .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
- }
-
- private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
- if (viewNode.getClassName() != null) {
- return false;
- }
- return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
- }
-
- private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
- String className = viewNode.getClassName();
- return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
- }
-
- private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
- return isSuspiciousText(ContentProtectionUtils.getEventText(event))
- || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
- }
-
- private boolean isSuspiciousText(@Nullable String text) {
- if (text == null) {
- return false;
- }
- if (isPasswordText(text)) {
- return true;
- }
- String lowerCaseText = text.toLowerCase();
- return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
- .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
- }
-
- private boolean isPasswordText(@Nullable String text) {
- if (text == null) {
- return false;
- }
- String lowerCaseText = text.toLowerCase();
- return PASSWORD_TEXTS.stream()
- .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
}
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
index 9abf6f1..1ecac7f 100644
--- a/core/java/android/view/contentprotection/ContentProtectionUtils.java
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -28,33 +28,39 @@
*/
public final class ContentProtectionUtils {
- /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+ /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */
@Nullable
- public static String getEventText(@NonNull ContentCaptureEvent event) {
+ public static String getEventTextLower(@NonNull ContentCaptureEvent event) {
CharSequence text = event.getText();
if (text == null) {
return null;
}
- return text.toString();
+ return text.toString().toLowerCase();
}
- /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+ /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */
@Nullable
- public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
- ViewNode viewNode = event.getViewNode();
+ public static String getViewNodeTextLower(@Nullable ViewNode viewNode) {
if (viewNode == null) {
return null;
}
- return getViewNodeText(viewNode);
- }
-
- /** Returns the text extracted directly from the {@link ViewNode}, if set. */
- @Nullable
- public static String getViewNodeText(@NonNull ViewNode viewNode) {
CharSequence text = viewNode.getText();
if (text == null) {
return null;
}
- return text.toString();
+ return text.toString().toLowerCase();
+ }
+
+ /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */
+ @Nullable
+ public static String getHintTextLower(@Nullable ViewNode viewNode) {
+ if (viewNode == null) {
+ return null;
+ }
+ String text = viewNode.getHint();
+ if (text == null) {
+ return null;
+ }
+ return text.toLowerCase();
}
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 56b5fac..fd96890 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -26,4 +26,11 @@
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
bug: "299946220"
+}
+
+flag {
+ name: "wm_display_refresh_rate_test"
+ namespace: "core_graphics"
+ description: "Adds WindowManager display refresh rate fields to test API"
+ bug: "304475199"
}
\ No newline at end of file
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index f53737a..5562360 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -49,7 +49,7 @@
private final int mSwipeEdge;
/**
- * Creates a new {@link BackMotionEvent} instance.
+ * Creates a new {@link BackEvent} instance.
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 5d14698..eb9d62b 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -263,6 +263,7 @@
Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady");
}
synchronized (mLock) {
+ toggleTimeout(false);
if (mHasWMSync) {
try {
WindowManagerGlobal.getWindowManagerService().markSurfaceSyncGroupReady(mToken);
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 43fa0be..4e0f9a5 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -88,6 +88,26 @@
*/
public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
+ /**
+ * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the
+ * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12;
+
+ /**
+ * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the
+ * TaskFragment to the top of the Task above all the other Activities and TaskFragments.
+ *
+ * This is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -101,7 +121,9 @@
OP_TYPE_SET_ANIMATION_PARAMS,
OP_TYPE_SET_RELATIVE_BOUNDS,
OP_TYPE_REORDER_TO_FRONT,
- OP_TYPE_SET_ISOLATED_NAVIGATION
+ OP_TYPE_SET_ISOLATED_NAVIGATION,
+ OP_TYPE_REORDER_TO_BOTTOM_OF_TASK,
+ OP_TYPE_REORDER_TO_TOP_OF_TASK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 611da3c..0cbfcc5 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -155,7 +155,7 @@
}
/**
- * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for
* low memory and trim memory events.
*/
@Override
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7c931cd..392aa1b 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -13,3 +13,11 @@
description: "On close to square display, when necessary, configuration includes status bar"
bug: "291870756"
}
+
+flag {
+ name: "dimmer_refactor"
+ namespace: "windowing_frontend"
+ description: "Refactor dim to fix flickers"
+ bug: "281632483,295291019"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 72a1bac..ca6c54d 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -24,6 +24,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
@@ -147,7 +148,8 @@
public void registerDisplayListener(DisplayManager.DisplayListener listener) {
manager.registerDisplayListener(listener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+ ActivityThread.currentPackageName());
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index d3103f1..0399430 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -37,7 +37,6 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -78,7 +77,7 @@
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- private static final int VERSION = 209;
+ private static final int VERSION = 210;
private static final String HISTORY_DIR = "battery-history";
private static final String FILE_SUFFIX = ".bh";
@@ -194,10 +193,11 @@
private int mNextHistoryTagIdx = 0;
private int mNumHistoryTagChars = 0;
private int mHistoryBufferLastPos = -1;
- private long mLastHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryElapsedRealtimeMs = 0;
private long mTrackRunningHistoryUptimeMs = 0;
- private long mHistoryBaseTimeMs;
+ private final MonotonicClock mMonotonicClock;
+ // Monotonic time when we started writing to the history buffer
+ private long mHistoryBufferStartTime;
private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();
private byte mLastHistoryStepLevel = 0;
private boolean mMutable = true;
@@ -307,23 +307,26 @@
* @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
*/
public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock) {
this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
- stepDetailsCalculator, clock, new TraceDelegate());
+ stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());
initHistoryBuffer();
}
@VisibleForTesting
public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer) {
this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
- clock, tracer, null);
+ clock, monotonicClock, tracer, null);
}
private BatteryStatsHistory(Parcel historyBuffer, File systemDir,
int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock, TraceDelegate tracer,
BatteryStatsHistory writableHistory) {
mHistoryBuffer = historyBuffer;
mSystemDir = systemDir;
@@ -332,6 +335,7 @@
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = tracer;
mClock = clock;
+ mMonotonicClock = monotonicClock;
mWritableHistory = writableHistory;
if (mWritableHistory != null) {
mMutable = false;
@@ -381,16 +385,18 @@
}
private BatteryHistoryFile makeBatteryHistoryFile() {
- return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs);
+ return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());
}
public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
- HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+ MonotonicClock monotonicClock) {
mMaxHistoryFiles = maxHistoryFiles;
mMaxHistoryBufferSize = maxHistoryBufferSize;
mStepDetailsCalculator = stepDetailsCalculator;
mTracer = new TraceDelegate();
mClock = clock;
+ mMonotonicClock = monotonicClock;
mHistoryBuffer = Parcel.obtain();
mSystemDir = null;
@@ -417,16 +423,16 @@
mHistoryBuffer = Parcel.obtain();
mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
+ mMonotonicClock = null;
readFromParcel(parcel, true /* useBlobs */);
}
private void initHistoryBuffer() {
- mHistoryBaseTimeMs = 0;
- mLastHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
mWrittenPowerStatsDescriptors.clear();
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -466,7 +472,7 @@
historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null,
- this);
+ null, this);
}
}
@@ -491,7 +497,7 @@
* When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},
* create next history file.
*/
- public void startNextFile() {
+ public void startNextFile(long elapsedRealtimeMs) {
if (mMaxHistoryFiles == 0) {
Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
return;
@@ -517,6 +523,7 @@
Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());
}
+ mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
@@ -699,9 +706,12 @@
if (mHistoryParcels != null) {
while (mParcelIndex < mHistoryParcels.size()) {
final Parcel p = mHistoryParcels.get(mParcelIndex++);
- if (!skipHead(p)) {
+ if (!verifyVersion(p)) {
continue;
}
+ // skip monotonic time field.
+ p.readLong();
+
final int bufSize = p.readInt();
final int curPos = p.dataPosition();
mCurrentParcelEnd = curPos + bufSize;
@@ -745,24 +755,36 @@
}
out.unmarshall(raw, 0, raw.length);
out.setDataPosition(0);
- return skipHead(out);
+ if (!verifyVersion(out)) {
+ return false;
+ }
+ // skip monotonic time field.
+ out.readLong();
+ return true;
}
/**
- * Skip the header part of history parcel.
+ * Verify header part of history parcel.
*
- * @param p history parcel to skip head.
* @return true if version match, false if not.
*/
- private boolean skipHead(Parcel p) {
+ private boolean verifyVersion(Parcel p) {
p.setDataPosition(0);
final int version = p.readInt();
- if (version != VERSION) {
- return false;
- }
- // skip historyBaseTime field.
- p.readLong();
- return true;
+ return version == VERSION;
+ }
+
+ /**
+ * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history
+ * buffer.
+ */
+ public long getHistoryBufferStartTime(Parcel p) {
+ int pos = p.dataPosition();
+ p.setDataPosition(0);
+ p.readInt(); // Skip the version field
+ long monotonicTime = p.readLong();
+ p.setDataPosition(pos);
+ return monotonicTime;
}
/**
@@ -1438,7 +1460,8 @@
throw new ConcurrentModificationException("Battery history is not writable");
}
- final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+ final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs)
+ - mHistoryLastWritten.time;
final int diffStates = mHistoryLastWritten.states ^ cur.states;
final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;
final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
@@ -1476,7 +1499,9 @@
mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
mHistoryBufferLastPos = -1;
- elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+
+ elapsedRealtimeMs -= timeDiffMs;
+
// If the last written history had a wakelock tag, we need to retain it.
// Note that the condition above made sure that we aren't in a case where
// both it and the current history item have a wakelock tag.
@@ -1513,7 +1538,7 @@
HistoryItem copy = new HistoryItem();
copy.setTo(cur);
- startNextFile();
+ startNextFile(elapsedRealtimeMs);
// startRecordingHistory will reset mHistoryCur.
startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
@@ -1548,7 +1573,7 @@
mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
mHistoryLastLastWritten.setTo(mHistoryLastWritten);
final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
- mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+ mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);
if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {
Slog.wtf(TAG, "Significantly earlier event written to battery history:"
+ " time=" + mHistoryLastWritten.time
@@ -1556,7 +1581,6 @@
}
mHistoryLastWritten.tagsFirstOccurrence = hasTags;
writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
cur.wakelockTag = null;
cur.wakeReasonTag = null;
cur.eventCode = HistoryItem.EVENT_NONE;
@@ -1926,6 +1950,10 @@
return;
}
+ // Save the monotonic time first, so that even if the history write below fails,
+ // we still wouldn't end up with overlapping history timelines.
+ mMonotonicClock.write();
+
Parcel p = Parcel.obtain();
try {
final long start = SystemClock.uptimeMillis();
@@ -1951,8 +1979,7 @@
return;
}
- final long historyBaseTime = in.readLong();
-
+ mHistoryBufferStartTime = in.readLong();
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -1972,39 +1999,11 @@
mHistoryBuffer.appendFrom(in, curPos, bufSize);
in.setDataPosition(curPos + bufSize);
}
-
- mHistoryBaseTimeMs = historyBaseTime;
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** NEW mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
-
- if (mHistoryBaseTimeMs > 0) {
- long elapsedRealtimeMs = mClock.elapsedRealtime();
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1;
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- }
}
private void writeHistoryBuffer(Parcel out) {
- if (DEBUG) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** WRITING mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- sb.append(" mLastHistoryElapsedRealtimeMs: ");
- TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
out.writeInt(BatteryStatsHistory.VERSION);
- out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+ out.writeLong(mHistoryBufferStartTime);
out.writeInt(mHistoryBuffer.dataSize());
if (DEBUG) {
Slog.i(TAG, "***************** WRITING HISTORY: "
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index a5d2d0f..6bd5898 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -16,7 +16,6 @@
package com.android.internal.os;
-import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -34,8 +33,8 @@
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistoryItr";
private final BatteryStatsHistory mBatteryStatsHistory;
- private final @CurrentTimeMillisLong long mStartTimeMs;
- private final @CurrentTimeMillisLong long mEndTimeMs;
+ private final long mStartTimeMs;
+ private final long mEndTimeMs;
private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
@@ -43,10 +42,10 @@
new PowerStats.DescriptorRegistry();
private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
private boolean mNextItemReady;
+ private boolean mTimeInitialized;
- public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history,
- @CurrentTimeMillisLong long startTimeMs,
- @CurrentTimeMillisLong long endTimeMs) {
+ public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs,
+ long endTimeMs) {
mBatteryStatsHistory = history;
mStartTimeMs = startTimeMs;
mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE;
@@ -82,7 +81,12 @@
break;
}
- final long lastRealtimeMs = mHistoryItem.time;
+ if (!mTimeInitialized) {
+ mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p);
+ mTimeInitialized = true;
+ }
+
+ final long lastMonotonicTimeMs = mHistoryItem.time;
final long lastWalltimeMs = mHistoryItem.currentTime;
try {
readHistoryDelta(p, mHistoryItem);
@@ -93,12 +97,13 @@
if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
&& mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET
&& lastWalltimeMs != 0) {
- mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+ mHistoryItem.currentTime =
+ lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);
}
- if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) {
+ if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {
break;
}
- if (mHistoryItem.currentTime >= mStartTimeMs) {
+ if (mHistoryItem.time >= mStartTimeMs) {
mNextItemReady = true;
return;
}
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 5ea6ba8..1f44b33 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -191,6 +191,27 @@
}
/**
+ * Sets the new values for the given state.
+ */
+ public void setValues(int state, long[] values) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException(
+ "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]");
+ }
+ if (values.length != mLength) {
+ throw new IllegalArgumentException(
+ "Invalid array length: " + values.length + ", expected: " + mLength);
+ }
+ LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
+ if (container == null || container.mLength != values.length) {
+ container = new LongArrayContainer(values.length);
+ }
+ container.setValues(values);
+ native_setValues(mNativeObject, state, container.mNativeObject);
+ sTmpArrayContainer.set(container);
+ }
+
+ /**
* Sets the new values. The delta between the previously set values and these values
* is distributed among the state according to the time the object spent in those states
* since the previous call to updateValues.
@@ -317,6 +338,10 @@
private static native void native_setState(long nativeObject, int state, long timestampMs);
@CriticalNative
+ private static native void native_setValues(long nativeObject, int state,
+ long longArrayContainerNativeObject);
+
+ @CriticalNative
private static native void native_updateValues(long nativeObject,
long longArrayContainerNativeObject, long timestampMs);
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
new file mode 100644
index 0000000..6f114e3
--- /dev/null
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -0,0 +1,145 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset
+ * on reboot, but keeps going.
+ */
+public class MonotonicClock {
+ private static final String TAG = "MonotonicClock";
+
+ private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time";
+ private static final String XML_ATTR_TIMESHIFT = "timeshift";
+
+ private final AtomicFile mFile;
+ private final Clock mClock;
+ private long mTimeshift;
+
+ public MonotonicClock(File file) {
+ mFile = new AtomicFile(file);
+ mClock = Clock.SYSTEM_CLOCK;
+ read();
+ }
+
+ public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ mClock = clock;
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that
+ * after a device reboot the time keeps increasing.
+ */
+ public long monotonicTime() {
+ return monotonicTime(mClock.elapsedRealtime());
+ }
+
+ /**
+ * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead
+ * of being read from the Clock.
+ */
+ public long monotonicTime(long elapsedRealtimeMs) {
+ return mTimeshift + elapsedRealtimeMs;
+ }
+
+ private void read() {
+ if (!mFile.exists()) {
+ return;
+ }
+
+ try {
+ readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ }
+ }
+
+ /**
+ * Saves the timeshift into a file. Call this method just before system shutdown, after
+ * writing the last battery history event.
+ */
+ public void write() {
+ if (mFile == null) {
+ return;
+ }
+
+ mFile.write(out -> {
+ try {
+ writeXml(out, Xml.newBinarySerializer());
+ out.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ }
+ });
+ }
+
+ /**
+ * Parses an XML file containing the persistent state of the monotonic clock.
+ */
+ @VisibleForTesting
+ public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ long savedTimeshift = 0;
+ try {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) {
+ savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT);
+ }
+ eventType = parser.next();
+ }
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the monotonic clock.
+ */
+ @VisibleForTesting
+ public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime());
+ serializer.endTag(null, XML_TAG_MONOTONIC_TIME);
+ serializer.endDocument();
+ }
+}
diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java
index f971849..dc5055a 100644
--- a/core/java/com/android/internal/os/MultiStateStats.java
+++ b/core/java/com/android/internal/os/MultiStateStats.java
@@ -16,9 +16,17 @@
package com.android.internal.os;
+import android.util.Slog;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -29,15 +37,21 @@
* values;
*/
public class MultiStateStats {
+ private static final String TAG = "MultiStateStats";
+
+ private static final String XML_TAG_STATS = "stats";
+
/**
* A set of states, e.g. on-battery, screen-on, procstate. The state values are integers
* from 0 to States.mLabels.length
*/
public static class States {
+ final String mName;
final boolean mTracked;
final String[] mLabels;
- public States(boolean tracked, String... labels) {
+ public States(String name, boolean tracked, String... labels) {
+ mName = name;
this.mTracked = tracked;
this.mLabels = labels;
}
@@ -155,11 +169,28 @@
>>> mStateBitFieldShifts[stateIndex];
}
- private int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
+ int setStateInComposite(int baseCompositeState, int stateIndex, int value) {
return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])
| (value << mStateBitFieldShifts[stateIndex]);
}
+ int setStateInComposite(int compositeState, String stateName, String stateLabel) {
+ for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) {
+ States stateConfig = mStates[stateIndex];
+ if (stateConfig.mName.equals(stateName)) {
+ for (int state = 0; state < stateConfig.mLabels.length; state++) {
+ if (stateConfig.mLabels[state].equals(stateLabel)) {
+ return setStateInComposite(compositeState, stateIndex, state);
+ }
+ }
+ Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName);
+ return -1;
+ }
+ }
+ Slog.e(TAG, "Unsupported state: " + stateName);
+ return -1;
+ }
+
/**
* Allocates a new stats container using this Factory's configuration.
*/
@@ -195,6 +226,10 @@
}
return serialState;
}
+
+ int getSerialState(int compositeState) {
+ return mCompositeToSerialState[compositeState];
+ }
}
private final Factory mFactory;
@@ -254,6 +289,106 @@
}
/**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray);
+ }
+
+ private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex,
+ long[] values) throws IOException {
+ if (stateIndex < states.length) {
+ if (!mFactory.mStates[stateIndex].mTracked) {
+ writeXmlAllStates(serializer, states, stateIndex + 1, values);
+ return;
+ }
+
+ for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) {
+ states[stateIndex] = i;
+ writeXmlAllStates(serializer, states, stateIndex + 1, values);
+ }
+ return;
+ }
+
+ mCounter.getCounts(values, mFactory.getSerialState(states));
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ serializer.startTag(null, XML_TAG_STATS);
+
+ for (int i = 0; i < states.length; i++) {
+ if (mFactory.mStates[i].mTracked && states[i] != 0) {
+ serializer.attribute(null, mFactory.mStates[i].mName,
+ mFactory.mStates[i].mLabels[states[i]]);
+ }
+ }
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != 0) {
+ serializer.attributeLong(null, "_" + i, values[i]);
+ }
+ }
+ serializer.endTag(null, XML_TAG_STATS);
+ }
+
+ /**
+ * Populates the object with contents in an XML doc. The parser is expected to be
+ * positioned on the opening tag of the corresponding element.
+ */
+ public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ String outerTag = parser.getName();
+ long[] tmpArray = new long[mCounter.getArrayLength()];
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(XML_TAG_STATS)) {
+ Arrays.fill(tmpArray, 0);
+ int compositeState = 0;
+ int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ String attributeName = parser.getAttributeName(i);
+ if (attributeName.startsWith("_")) {
+ int index;
+ try {
+ index = Integer.parseInt(attributeName.substring(1));
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Unexpected index syntax: " + attributeName, parser, e);
+ }
+ if (index < 0 || index >= tmpArray.length) {
+ Slog.e(TAG, "State index out of bounds: " + index
+ + " length: " + tmpArray.length);
+ return false;
+ }
+ tmpArray[index] = parser.getAttributeLong(i);
+ } else {
+ String attributeValue = parser.getAttributeValue(i);
+ compositeState = mFactory.setStateInComposite(compositeState,
+ attributeName, attributeValue);
+ if (compositeState == -1) {
+ return false;
+ }
+ }
+ }
+ mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray);
+ }
+ }
+ eventType = parser.next();
+ }
+ return true;
+ }
+
+ /**
* Prints the accumulated stats, one line of every combination of states that has data.
*/
public void dump(PrintWriter pw) {
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index e35b7f1..996e424 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -5,14 +5,10 @@
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
-per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS
per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
-per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS
-per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
-per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
per-file *Kernel* = file:/BATTERY_STATS_OWNERS
+per-file *Clock* = file:/BATTERY_STATS_OWNERS
per-file *MultiState* = file:/BATTERY_STATS_OWNERS
per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
+per-file *PowerStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 8f66d1f..1130a45 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -24,9 +24,16 @@
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
@@ -61,6 +68,13 @@
* to adjust the algorithm in accordance with the stats available on the device.
*/
public static class Descriptor {
+ public static final String XML_TAG_DESCRIPTOR = "descriptor";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_NAME = "name";
+ private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
+ private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
+ private static final String XML_TAG_EXTRAS = "extras";
+
/**
* {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
* to; or a custom power component ID (if the value
@@ -126,7 +140,7 @@
int firstWord = parcel.readInt();
int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
if (version != PARCEL_FORMAT_VERSION) {
- Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
return null;
}
@@ -155,6 +169,71 @@
that.extras); // Since the Parcel is now unparceled, do a deep comparison
}
+ /**
+ * Stores contents in an XML doc.
+ */
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_DESCRIPTOR);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ serializer.attribute(null, XML_ATTR_NAME, name);
+ serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
+ serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
+ try {
+ serializer.startTag(null, XML_TAG_EXTRAS);
+ extras.saveToXml(serializer);
+ serializer.endTag(null, XML_TAG_EXTRAS);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ serializer.endTag(null, XML_TAG_DESCRIPTOR);
+ }
+
+ /**
+ * Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned
+ * on or before the opening "descriptor" tag.
+ */
+ public static Descriptor createFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int powerComponentId = -1;
+ String name = null;
+ int statsArrayLength = 0;
+ int uidStatsArrayLength = 0;
+ PersistableBundle extras = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_DESCRIPTOR))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_DESCRIPTOR:
+ powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
+ name = parser.getAttributeValue(null, XML_ATTR_NAME);
+ statsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_STATS_ARRAY_LENGTH);
+ uidStatsArrayLength = parser.getAttributeInt(null,
+ XML_ATTR_UID_STATS_ARRAY_LENGTH);
+ break;
+ case XML_TAG_EXTRAS:
+ extras = PersistableBundle.restoreFromXml(parser);
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ if (powerComponentId == -1) {
+ return null;
+ } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength,
+ extras);
+ } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
+ return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength,
+ extras);
+ } else {
+ Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
+ return null;
+ }
+ }
+
@Override
public int hashCode() {
return Objects.hash(powerComponentId);
@@ -259,7 +338,7 @@
Descriptor descriptor = registry.get(powerComponentId);
if (descriptor == null) {
- Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
+ Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
return null;
}
PowerStats stats = new PowerStats(descriptor);
@@ -274,7 +353,7 @@
stats.uidStats.put(uid, uidStats);
}
if (parcel.dataPosition() != endPos) {
- Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ ", actual length: " + (parcel.dataPosition() - startPos));
return null;
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ec525f0..4bb7c33 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -91,6 +91,8 @@
WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
"CoreBackPreview"),
WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
+
+ WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index 6920211..1f29735 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -55,6 +55,15 @@
counter->setState(state, timestamp);
}
+static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
+ battery::LongArrayMultiStateCounter *counter =
+ reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ std::vector<uint64_t> *vector =
+ reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
+
+ counter->setValue(state, *vector);
+}
+
static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
jlong timestamp) {
battery::LongArrayMultiStateCounter *counter =
@@ -210,6 +219,8 @@
// @CriticalNative
{"native_setState", "(JIJ)V", (void *)native_setState},
// @CriticalNative
+ {"native_setValues", "(JIJ)V", (void *)native_setValues},
+ // @CriticalNative
{"native_updateValues", "(JJJ)V", (void *)native_updateValues},
// @CriticalNative
{"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 84690a7..88b578b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7237,7 +7237,7 @@
<!-- @SystemApi Required for the privileged assistant apps targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- that receive voice trigger from a trusted hotword detection service.
+ that receive voice trigger from a sandboxed {@link HotwordDetectionService}.
<p>Protection level: signature|privileged|appop
@FlaggedApi("android.permission.flags.voice_activation_permission_apis")
@hide -->
@@ -7246,8 +7246,8 @@
<!-- @SystemApi Required for the privileged assistant apps targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- that receive training data from the sandboxed hotword detection service or visual
- query detection service.
+ that receive training data from a sandboxed {@link HotwordDetectionService} or
+ {@link VisualQueryDetectionService}.
<p>Protection level: internal|appop
@FlaggedApi("android.permission.flags.voice_activation_permission_apis")
@hide -->
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8fb48bc..e42962c 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -31,4 +31,13 @@
is a relatively expensive operation, this throttle period may need to be adjusted for low-power
devices-->
<integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+
+ <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
+ stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
+ <integer name="config_powerStatsAggregationPeriod">14400000</integer>
+
+ <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery
+ history time for every aggregated power stats span that is stored stored in PowerStatsStore.
+ It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) -->
+ <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 41356bd..a2a4e34f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6298,6 +6298,11 @@
<!-- Content of connected display unavailable notification. [CHAR LIMIT=NONE] -->
<string name="connected_display_unavailable_notification_content">Use a different cable and try again</string>
+ <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string>
+ <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] -->
+ <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string>
+
<!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
<string name="concurrent_display_notification_name">Dual screen</string>
<!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index efe5c3b..76744ea 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5070,6 +5070,8 @@
<java-symbol type="array" name="device_state_notification_power_save_contents"/>
<java-symbol type="string" name="connected_display_unavailable_notification_title"/>
<java-symbol type="string" name="connected_display_unavailable_notification_content"/>
+ <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/>
+ <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/>
<java-symbol type="string" name="concurrent_display_notification_name"/>
<java-symbol type="string" name="concurrent_display_notification_active_title"/>
<java-symbol type="string" name="concurrent_display_notification_active_content"/>
@@ -5122,6 +5124,8 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
<java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
+ <java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
+ <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 149f58f..c2e6b60c 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -42,8 +42,8 @@
public class DisplayManagerGlobalTest {
private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
- | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
@Mock
private IDisplayManager mDisplayManager;
@@ -69,7 +69,8 @@
@Test
public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException {
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
@@ -97,26 +98,27 @@
public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
// First we subscribe to all events in order to test that the subsequent calls to
// registerDisplayListener will update the event mask.
- mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS);
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS,
+ null);
Mockito.verify(mDisplayManager)
.registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
IDisplayManagerCallback callback = mCallbackCaptor.getValue();
int displayId = 1;
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+ ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);
callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
waitForHandler();
Mockito.verifyZeroInteractions(mListener);
@@ -139,7 +141,7 @@
public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
throws RemoteException {
mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
- DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);
InOrder inOrder = Mockito.inOrder(mDisplayManager);
inOrder.verify(mDisplayManager)
@@ -163,6 +165,7 @@
}
private void waitForHandler() {
- mHandler.runWithScissors(() -> { }, 0);
+ mHandler.runWithScissors(() -> {
+ }, 0);
}
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index e76d266..d47d789 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -115,6 +115,26 @@
new ContentCaptureOptions.ContentProtectionOptions(
/* enableReceiver= */ true,
-BUFFER_SIZE,
+ /* requiredGroups= */ List.of(List.of("a")),
+ /* optionalGroups= */ Collections.emptyList(),
+ /* optionalGroupsThreshold= */ 0));
+ MainContentCaptureSession session = createSession(options);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true,
+ BUFFER_SIZE,
/* requiredGroups= */ Collections.emptyList(),
/* optionalGroups= */ Collections.emptyList(),
/* optionalGroupsThreshold= */ 0));
@@ -320,7 +340,7 @@
new ContentCaptureOptions.ContentProtectionOptions(
enableContentProtectionReceiver,
BUFFER_SIZE,
- /* requiredGroups= */ Collections.emptyList(),
+ /* requiredGroups= */ List.of(List.of("a")),
/* optionalGroups= */ Collections.emptyList(),
/* optionalGroupsThreshold= */ 0));
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index 39a2e0e..ba0dbf4 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -29,12 +29,13 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
-import android.os.Looper;
-import android.text.InputType;
+import android.os.test.TestLooper;
import android.view.View;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.IContentCaptureManager;
@@ -57,6 +58,7 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.mockito.verification.VerificationMode;
import java.time.Instant;
import java.util.ArrayList;
@@ -75,13 +77,25 @@
private static final String PACKAGE_NAME = "com.test.package.name";
- private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+ private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT";
- private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+ private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT";
- private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+ private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT";
- private static final String SAFE_TEXT = "SAFE TEXT";
+ private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT";
+
+ private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT";
+
+ private static final String TEXT_SHARED = "TEXT SHARED TEXT";
+
+ private static final String TEXT_SAFE = "TEXT SAFE TEXT";
+
+ private static final List<List<String>> REQUIRED_GROUPS =
+ List.of(List.of("required1", "missing"), List.of("required2", "shared"));
+
+ private static final List<List<String>> OPTIONAL_GROUPS =
+ List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared"));
private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
@@ -91,7 +105,17 @@
private static final Set<Integer> EVENT_TYPES_TO_STORE =
ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
- private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+ private static final int BUFFER_SIZE = 150;
+
+ private static final int OPTIONAL_GROUPS_THRESHOLD = 1;
+
+ private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS =
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true,
+ BUFFER_SIZE,
+ REQUIRED_GROUPS,
+ OPTIONAL_GROUPS,
+ OPTIONAL_GROUPS_THRESHOLD);
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -101,16 +125,19 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
- private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+ private final TestLooper mTestLooper = new TestLooper();
+
+ @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor;
@Before
public void setup() {
mContentProtectionEventProcessor =
new ContentProtectionEventProcessor(
mMockEventBuffer,
- new Handler(Looper.getMainLooper()),
+ new Handler(mTestLooper.getLooper()),
mMockContentCaptureManager,
- PACKAGE_NAME);
+ PACKAGE_NAME,
+ OPTIONS);
}
@Test
@@ -156,347 +183,224 @@
}
@Test
- public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ public void processEvent_loginDetected_true_eventText() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ TEXT_REQUIRED1,
+ /* viewNodeText= */ null,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ TEXT_REQUIRED2,
+ /* viewNodeText= */ null,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ TEXT_OPTIONAL1,
+ /* viewNodeText= */ null,
+ /* hintText= */ null));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_viewNodeText() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ TEXT_REQUIRED1,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ TEXT_REQUIRED2,
+ /* hintText= */ null));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ TEXT_OPTIONAL1,
+ /* hintText= */ null));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_hintText() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ null,
+ /* hintText= */ TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ null,
+ /* hintText= */ TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(
+ createProcessEvent(
+ /* eventText= */ null,
+ /* viewNodeText= */ null,
+ /* hintText= */ TEXT_OPTIONAL1));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_usesContains() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_missingRequiredGroups() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_missingOptionalGroups() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_safeText() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_sharedTextOnce() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+ assertLoginNotDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+ assertLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
for (int type = -100; type <= 100; type++) {
if (type == TYPE_VIEW_APPEARED) {
continue;
}
-
- mContentProtectionEventProcessor.processEvent(createEvent(type));
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ ContentCaptureEvent event = createEvent(type);
+ event.setText(TEXT_OPTIONAL1);
+ mContentProtectionEventProcessor.processEvent(event);
}
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
+ assertLoginNotDetected();
}
@Test
- public void processEvent_loginDetected() throws Exception {
+ public void processEvent_loginDetected_true_belowResetLimit() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected();
- }
-
- @Test
- public void processEvent_loginDetected_passwordFieldNotDetected() {
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void processEvent_loginDetected_suspiciousTextNotDetected() {
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void processEvent_loginDetected_withoutViewNode() {
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void processEvent_loginDetected_belowResetLimit() throws Exception {
- when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+ for (int i = 0; i < BUFFER_SIZE - 2; i++) {
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
}
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
+ assertLoginNotDetected();
- mContentProtectionEventProcessor.processEvent(event);
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected();
+ assertLoginDetected();
}
@Test
- public void processEvent_loginDetected_aboveResetLimit() throws Exception {
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ public void processEvent_loginDetected_false_aboveResetLimit() {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
- for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+ for (int i = 0; i < BUFFER_SIZE - 1; i++) {
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
}
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
+ assertLoginNotDetected();
- mContentProtectionEventProcessor.processEvent(event);
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
+ assertLoginNotDetected();
}
@Test
public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ for (int i = 0; i < 2; i++) {
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ }
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
- mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected();
+ assertLoginDetected();
}
@Test
public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
- mContentProtectionEventProcessor.mPasswordFieldDetected = true;
- mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+ mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, times(2)).clear();
- verify(mMockEventBuffer, times(2)).toArray();
- assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
- }
-
- @Test
- public void isPasswordField_android() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_android_withoutClassName() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_android_wrongClassName() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- "wrong.prefix" + ANDROID_CLASS_NAME,
- InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_android_wrongInputType() {
- ContentCaptureEvent event =
- createAndroidPasswordFieldEvent(
- ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_webView() throws Exception {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(
- /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
- when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer).clear();
- verify(mMockEventBuffer).toArray();
- assertOnLoginDetected(event, /* times= */ 1);
- }
-
- @Test
- public void isPasswordField_webView_withClassName() {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(
- /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_webView_withSafeViewNodeText() {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(
- /* className= */ null, /* eventText= */ null, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isPasswordField_webView_withEventText() {
- ContentCaptureEvent event =
- createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_withSafeText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_eventText_suspiciousText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_viewNodeText_suspiciousText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_eventText_passwordText() {
- ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
- }
-
- @Test
- public void isSuspiciousText_viewNodeText_passwordText() {
- // Specify the class to differ from {@link isPasswordField_webView} test in this version
- ContentCaptureEvent event =
- createProcessEvent(
- "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
-
- mContentProtectionEventProcessor.processEvent(event);
-
- assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
- verify(mMockEventBuffer, never()).clear();
- verify(mMockEventBuffer, never()).toArray();
- verifyZeroInteractions(mMockContentCaptureManager);
+ assertLoginDetected(times(2));
}
private static ContentCaptureEvent createEvent(int type) {
@@ -511,20 +415,20 @@
return createEvent(TYPE_VIEW_APPEARED);
}
+ private ContentCaptureEvent createProcessEvent(@Nullable String eventText) {
+ return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null);
+ }
+
private ContentCaptureEvent createProcessEvent(
- @Nullable String className,
- int inputType,
- @Nullable String eventText,
- @Nullable String viewNodeText) {
+ @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) {
View view = new View(mContext);
ViewStructureImpl viewStructure = new ViewStructureImpl(view);
- if (className != null) {
- viewStructure.setClassName(className);
- }
if (viewNodeText != null) {
viewStructure.setText(viewNodeText);
}
- viewStructure.setInputType(inputType);
+ if (hintText != null) {
+ viewStructure.setHint(hintText);
+ }
ContentCaptureEvent event = createProcessEvent();
event.setViewNode(viewStructure.getNode());
@@ -535,34 +439,28 @@
return event;
}
- private ContentCaptureEvent createAndroidPasswordFieldEvent(
- @Nullable String className, int inputType) {
- return createProcessEvent(
- className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+ private void assertLoginNotDetected() {
+ mTestLooper.dispatchAll();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
}
- private ContentCaptureEvent createWebViewPasswordFieldEvent(
- @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
- return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+ private void assertLoginDetected() throws Exception {
+ assertLoginDetected(times(1));
}
- private ContentCaptureEvent createSuspiciousTextEvent(
- @Nullable String eventText, @Nullable String viewNodeText) {
- return createProcessEvent(
- /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
- }
+ private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception {
+ mTestLooper.dispatchAll();
+ verify(mMockEventBuffer, verificationMode).clear();
+ verify(mMockEventBuffer, verificationMode).toArray();
- private void assertOnLoginDetected() throws Exception {
- assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
- }
-
- private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
- verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+ verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture());
assertThat(captor.getValue()).isNotNull();
List<ContentCaptureEvent> actual = captor.getValue().getList();
assertThat(actual).isNotNull();
- assertThat(actual).containsExactly(event);
+ assertThat(actual).containsExactly(PROCESS_EVENT);
}
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
index 1459799..fbe478e 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -44,68 +44,74 @@
private static final String TEXT = "TEST_TEXT";
- private static final ContentCaptureEvent EVENT = createEvent();
-
- private static final ViewNode VIEW_NODE = new ViewNode();
-
- private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+ private static final String TEXT_LOWER = TEXT.toLowerCase();
@Test
- public void event_getEventText_null() {
- String actual = ContentProtectionUtils.getEventText(EVENT);
+ public void getEventTextLower_null() {
+ String actual = ContentProtectionUtils.getEventTextLower(createEvent());
assertThat(actual).isNull();
}
@Test
- public void event_getEventText_notNull() {
- ContentCaptureEvent event = createEvent();
- event.setText(TEXT);
+ public void getEventTextLower_notNull() {
+ String actual = ContentProtectionUtils.getEventTextLower(createEventWithText());
- String actual = ContentProtectionUtils.getEventText(event);
-
- assertThat(actual).isEqualTo(TEXT);
+ assertThat(actual).isEqualTo(TEXT_LOWER);
}
@Test
- public void event_getViewNodeText_null() {
- String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+ public void getViewNodeTextLower_null() {
+ String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode());
assertThat(actual).isNull();
}
@Test
- public void event_getViewNodeText_notNull() {
- ContentCaptureEvent event = createEvent();
- event.setViewNode(VIEW_NODE_WITH_TEXT);
+ public void getViewNodeTextLower_notNull() {
+ String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText());
- String actual = ContentProtectionUtils.getViewNodeText(event);
-
- assertThat(actual).isEqualTo(TEXT);
+ assertThat(actual).isEqualTo(TEXT_LOWER);
}
@Test
- public void viewNode_getViewNodeText_null() {
- String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+ public void getHintTextLower_null() {
+ String actual = ContentProtectionUtils.getHintTextLower(new ViewNode());
assertThat(actual).isNull();
}
@Test
- public void viewNode_getViewNodeText_notNull() {
- String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+ public void getHintTextLower_notNull() {
+ String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint());
- assertThat(actual).isEqualTo(TEXT);
+ assertThat(actual).isEqualTo(TEXT_LOWER);
}
private static ContentCaptureEvent createEvent() {
return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
}
- private static ViewNode createViewNodeWithText() {
+ private static ContentCaptureEvent createEventWithText() {
+ ContentCaptureEvent event = createEvent();
+ event.setText(TEXT);
+ return event;
+ }
+
+ private static ViewStructureImpl createViewStructureImpl() {
View view = new View(ApplicationProvider.getApplicationContext());
- ViewStructureImpl viewStructure = new ViewStructureImpl(view);
- viewStructure.setText(TEXT);
- return viewStructure.getNode();
+ return new ViewStructureImpl(view);
+ }
+
+ private static ViewNode createViewNodeWithText() {
+ ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+ viewStructureImpl.setText(TEXT);
+ return viewStructureImpl.getNode();
+ }
+
+ private static ViewNode createViewNodeWithHint() {
+ ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+ viewStructureImpl.setHint(TEXT);
+ return viewStructureImpl.getNode();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 2cbeaa1..f846ac5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -34,6 +34,7 @@
KernelSingleUidTimeReaderTest.class,
LongArrayMultiStateCounterTest.class,
LongMultiStateCounterTest.class,
+ MonotonicClockTest.class,
PowerProfileTest.class,
PowerStatsTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
new file mode 100644
index 0000000..7951270
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MonotonicClockTest {
+ private final MockClock mClock = new MockClock();
+
+ @Test
+ public void persistence() throws IOException {
+ MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ monotonicClock.writeXml(out, Xml.newFastSerializer());
+ String xml = out.toString();
+ assertThat(xml).contains("timeshift=\"1234\"");
+
+ mClock.realtime = 42;
+ MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
+ newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()),
+ Xml.newFastPullParser());
+
+ mClock.realtime = 2000;
+ assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 9c65287..b71aaf3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1801,6 +1801,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-504637678": {
+ "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"-503656156": {
"message": "Update process config of %s to new config %s",
"level": "VERBOSE",
@@ -3739,6 +3745,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityClientController.java"
},
+ "1309365288": {
+ "message": "Removing dim surface %s on transaction %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"1316533291": {
"message": "State movement: %s from:%s to:%s reason:%s",
"level": "VERBOSE",
@@ -4003,6 +4015,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1620751818": {
+ "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_DIMMER",
+ "at": "com\/android\/server\/wm\/SmoothDimmer.java"
+ },
"1621562070": {
"message": " startWCT=%s",
"level": "VERBOSE",
@@ -4560,6 +4578,9 @@
"WM_DEBUG_CONTENT_RECORDING": {
"tag": "WindowManager"
},
+ "WM_DEBUG_DIMMER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_DRAW": {
"tag": "WindowManager"
},
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index db1cc44..9fde0fd 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1609,7 +1609,7 @@
/**
* Returns the color of the shadow layer.
*
- * @return the shadow layer's color encoded as a {@link ColorLong}.
+ * @return the shadow layer's color encoded as a {@code ColorLong}.
* @see #setShadowLayer(float,float,float,int)
* @see #setShadowLayer(float,float,float,long)
* @see Color
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index dc2e794..0e3fb16 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -249,7 +249,7 @@
* @param useBoundsForWidth True for using bounding box, false for advances.
* @return this builder instance
* @see Layout#getUseBoundsForWidth()
- * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
+ * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)
*/
@FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0e198d5..e6de597 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -322,7 +322,7 @@
}
/**
- * Returns the {@link CopyResultStatus} of the copy request.
+ * Returns the status of the copy request.
*/
public @CopyResultStatus int getStatus() {
return mStatus;
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 31c2eb2..b7ea04f 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -128,25 +128,6 @@
}
/**
- * Queries user state from Keystore 2.0.
- *
- * @param userId - Android user id of the user.
- * @return UserState enum variant as integer if successful or an error
- */
- public static int getState(int userId) {
- StrictMode.noteDiskRead();
- try {
- return getService().getState(userId);
- } catch (ServiceSpecificException e) {
- Log.e(TAG, "getState failed", e);
- return e.errorCode;
- } catch (Exception e) {
- Log.e(TAG, "Can not connect to keystore", e);
- return SYSTEM_ERROR;
- }
- }
-
- /**
* Informs Keystore 2.0 that an off body event was detected.
*/
public static void onDeviceOffBody() {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8045f55..11b8271 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,8 +19,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.StrictMode;
-import android.os.UserHandle;
-import android.security.maintenance.UserState;
/**
* @hide This should not be made public in its present form because it
@@ -37,15 +35,6 @@
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
- // States
- public enum State {
- @UnsupportedAppUsage
- UNLOCKED,
- @UnsupportedAppUsage
- LOCKED,
- UNINITIALIZED
- };
-
private static final KeyStore KEY_STORE = new KeyStore();
@UnsupportedAppUsage
@@ -55,28 +44,6 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public State state(int userId) {
- int userState = AndroidKeyStoreMaintenance.getState(userId);
- switch (userState) {
- case UserState.UNINITIALIZED:
- return KeyStore.State.UNINITIALIZED;
- case UserState.LSKF_UNLOCKED:
- return KeyStore.State.UNLOCKED;
- case UserState.LSKF_LOCKED:
- return KeyStore.State.LOCKED;
- default:
- throw new AssertionError(userState);
- }
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public State state() {
- return state(UserHandle.myUserId());
- }
-
- /** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public byte[] get(String key) {
return null;
}
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 253d704..5825fac 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -319,7 +319,7 @@
/**
* Returns one of the error codes exported by the class.
*
- * @return a public error code, one of the values in {@link PublicErrorCode}.
+ * @return a public error code
*/
@PublicErrorCode
public int getNumericErrorCode() {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 96c257b..1ba41b1 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -75,16 +75,18 @@
* {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
* interfaces.
*
- * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
- * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not
- * support storing key pairs without a certificate. The subject, serial number, and validity dates
- * of the certificate can be customized in this spec. The self-signed certificate may be replaced at
- * a later time by a certificate signed by a Certificate Authority (CA).
+ * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android
+ * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing
+ * key pairs without a certificate. The subject, serial number, and validity dates of the
+ * certificate can be customized in this spec. The certificate may be replaced at a later time by a
+ * certificate signed by a Certificate Authority (CA).
*
- * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
- * certificate will be created with an invalid signature which will not verify. Such a certificate
- * is still useful because it provides access to the public key. To generate a valid signature for
- * the certificate the key needs to be authorized for all of the following:
+ * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])},
+ * generated certificate may be self-signed. If a private key is not authorized to sign the
+ * certificate, then the certificate will be created with an invalid signature which will not
+ * verify. Such a certificate is still useful because it provides access to the public key. To
+ * generate a valid signature for the certificate the key needs to be authorized for all of the
+ * following:
* <ul>
* <li>{@link KeyProperties#PURPOSE_SIGN},</li>
* <li>operation without requiring the user to be authenticated (see
@@ -989,12 +991,6 @@
* @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
* used. Attempts to use the key for any other purpose will be rejected.
*
- * <p>If the set of purposes for which the key can be used does not contain
- * {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by
- * {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an
- * invalid signature. This is OK if the certificate is only used for obtaining the
- * public key from Android KeyStore.
- *
* <p>See {@link KeyProperties}.{@code PURPOSE} flags.
*/
public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
@@ -1140,7 +1136,7 @@
}
/**
- * Sets the subject used for the self-signed certificate of the generated key pair.
+ * Sets the subject used for the certificate of the generated key pair.
*
* <p>By default, the subject is {@code CN=fake}.
*/
@@ -1154,7 +1150,7 @@
}
/**
- * Sets the serial number used for the self-signed certificate of the generated key pair.
+ * Sets the serial number used for the certificate of the generated key pair.
*
* <p>By default, the serial number is {@code 1}.
*/
@@ -1168,8 +1164,7 @@
}
/**
- * Sets the start of the validity period for the self-signed certificate of the generated
- * key pair.
+ * Sets the start of the validity period for the certificate of the generated key pair.
*
* <p>By default, this date is {@code Jan 1 1970}.
*/
@@ -1183,8 +1178,7 @@
}
/**
- * Sets the end of the validity period for the self-signed certificate of the generated key
- * pair.
+ * Sets the end of the validity period for the certificate of the generated key pair.
*
* <p>By default, this date is {@code Jan 1 2048}.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 06ce371..8cf869b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -87,33 +87,28 @@
mTransitions.addHandler(this);
}
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean containsEmbeddingSplit = false;
- boolean containsNonEmbeddedChange = false;
- final List<TransitionInfo.Change> changes = info.getChanges();
- for (int i = changes.size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = changes.get(i);
- if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- containsNonEmbeddedChange = true;
- } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
+ /** Whether ActivityEmbeddingController should animate this transition. */
+ public boolean shouldAnimate(@NonNull TransitionInfo info) {
+ boolean containsEmbeddingChange = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
+ FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
- containsEmbeddingSplit = true;
+ containsEmbeddingChange = true;
}
}
- if (!containsEmbeddingSplit) {
+ if (!containsEmbeddingChange) {
// Let the system to play the default animation if there is no ActivityEmbedding split
// window. This allows to play the app customized animation when there is no embedding,
// such as the device is in a folded state.
return false;
}
- if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+
+ if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
return false;
}
+
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
if (options != null
// Scene-transition will be handled by app side.
@@ -123,6 +118,17 @@
return false;
}
+ return true;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+
+ if (!shouldAnimate(info)) return false;
+
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +142,16 @@
mAnimationRunner.cancelAnimationFromMerge();
}
+ /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
+ private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 14a040a..a533ca5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -366,11 +367,12 @@
KeyguardTransitionHandler keyguardTransitionHandler,
Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
pipTransitionController, recentsTransitionHandler,
keyguardTransitionHandler, desktopTasksController,
- unfoldHandler);
+ unfoldHandler, activityEmbeddingController);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index c508d55..8a64037 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,13 @@
package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
-import android.content.Context;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
-import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipTransition;
-import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -48,21 +42,8 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PipController pipController) {
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
pipBoundsAlgorithm);
}
-
- @WMSingleton
- @Provides
- static PipController providePipController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
- return PipController.create(context, shellInit, shellController, displayController,
- displayInsetsController, pipDisplayLayoutState);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
deleted file mode 100644
index 186cb61..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.wm.shell.pip2.phone;
-
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.view.InsetsState;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.pip.PipDisplayLayoutState;
-import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-
-/**
- * Manages the picture-in-picture (PIP) UI and states for Phones.
- */
-public class PipController implements ConfigurationChangeListener,
- DisplayController.OnDisplaysChangedListener {
- private static final String TAG = PipController.class.getSimpleName();
-
- private Context mContext;
- private ShellController mShellController;
- private DisplayController mDisplayController;
- private DisplayInsetsController mDisplayInsetsController;
- private PipDisplayLayoutState mPipDisplayLayoutState;
-
- private PipController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
- mContext = context;
- mShellController = shellController;
- mDisplayController = displayController;
- mDisplayInsetsController = displayInsetsController;
- mPipDisplayLayoutState = pipDisplayLayoutState;
-
- if (PipUtils.isPip2ExperimentEnabled()) {
- shellInit.addInitCallback(this::onInit, this);
- }
- }
-
- private void onInit() {
- // Ensure that we have the display info in case we get calls to update the bounds before the
- // listener calls back
- mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
- DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipDisplayLayoutState.setDisplayLayout(layout);
-
- mShellController.addConfigurationChangeListener(this);
- mDisplayController.addDisplayWindowListener(this);
- mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
- new DisplayInsetsController.OnInsetsChangedListener() {
- @Override
- public void insetsChanged(InsetsState insetsState) {
- onDisplayChanged(mDisplayController
- .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
- }
- });
- }
-
- /**
- * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
- */
- public static PipController create(Context context,
- ShellInit shellInit,
- ShellController shellController,
- DisplayController displayController,
- DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
- if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Device doesn't support Pip feature", TAG);
- return null;
- }
- return new PipController(context, shellInit, shellController, displayController,
- displayInsetsController, pipDisplayLayoutState);
- }
-
-
- @Override
- public void onConfigurationChanged(Configuration newConfiguration) {
- mPipDisplayLayoutState.onConfigurationChanged();
- }
-
- @Override
- public void onThemeChanged() {
- onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- if (displayId != mPipDisplayLayoutState.getDisplayId()) {
- return;
- }
- onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
- }
-
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != mPipDisplayLayoutState.getDisplayId()) {
- return;
- }
- onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
- }
-
- private void onDisplayChanged(DisplayLayout layout) {
- mPipDisplayLayoutState.setDisplayLayout(layout);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d31476c..d277eef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -925,19 +925,8 @@
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome
- // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
- // live tile (pausing app). If the gesture is "cancelled" we need to return to
- // 3p launcher instead of "task-switching" away from it.
- && (!mWillFinishToHome || mPausingSeparateHome)
- && mPausingTasks != null && mState == STATE_NORMAL) {
- if (mPausingSeparateHome) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to 3p home");
- } else {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " returning to app");
- }
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app");
// The gesture is returning to the pausing-task(s) rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -969,6 +958,15 @@
wct.restoreTransientOrder(mRecentsTask);
}
} else {
+ if (mPausingSeparateHome) {
+ if (mOpeningTasks.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " recents occluded 3p home");
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " switch task by recents on 3p home");
+ }
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " normal finish");
// The general case: committing to recents, going home, or switching tasks.
for (int i = 0; i < mOpeningTasks.size(); ++i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 68ca231..7a4834c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -23,6 +23,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -395,6 +396,13 @@
return mMainStage.isActive();
}
+ /** @return whether this transition-request has the launch-adjacent flag. */
+ public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ return triggerTask != null && triggerTask.baseIntent != null
+ && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0;
+ }
+
/** @return whether the transition-request implies entering pip from split. */
public boolean requestImpliesSplitToPip(TransitionRequestInfo request) {
if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) {
@@ -2459,10 +2467,20 @@
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
- // When split in the background, it should be only opening/dismissing transition and
- // would keep out not empty. Prevent intercepting all transitions for split screen when
- // it is in the background and not identify to handle it.
- return (!out.isEmpty() || isSplitScreenVisible()) ? out : null;
+ if (!out.isEmpty()) {
+ // One of the cases above handled it
+ return out;
+ } else if (isSplitScreenVisible()) {
+ // If split is visible, only defer handling this transition if it's launching
+ // adjacent while there is already a split pair -- this may trigger PIP and
+ // that should be handled by the mixed handler.
+ final boolean deferTransition = requestHasLaunchAdjacentFlag(request)
+ && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0;
+ return !deferTransition ? out : null;
+ }
+ // Don't intercept the transition if we are not handling it as a part of one of the
+ // cases above and it is not already visible
+ return null;
} else {
if (isOpening && getStageOfTask(triggerTask) != null) {
// One task is appearing into split, prepare to enter split screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 451e618..918a5a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,7 +21,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -43,6 +45,7 @@
import android.window.WindowContainerTransaction;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -74,6 +77,7 @@
private final KeyguardTransitionHandler mKeyguardHandler;
private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
+ private ActivityEmbeddingController mActivityEmbeddingController;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -93,9 +97,12 @@
/** Recents Transition while in desktop mode. */
static final int TYPE_RECENTS_DURING_DESKTOP = 6;
- /** Fuld/Unfold transition. */
+ /** Fold/Unfold transition. */
static final int TYPE_UNFOLD = 7;
+ /** Enter pip from one of the Activity Embedding windows. */
+ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -150,7 +157,8 @@
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
Optional<DesktopTasksController> desktopTasksControllerOptional,
- Optional<UnfoldTransitionHandler> unfoldHandler) {
+ Optional<UnfoldTransitionHandler> unfoldHandler,
+ Optional<ActivityEmbeddingController> activityEmbeddingController) {
mPlayer = player;
mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -170,6 +178,7 @@
}
mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
+ mActivityEmbeddingController = activityEmbeddingController.orElse(null);
}, this);
}
}
@@ -192,6 +201,16 @@
mPipHandler.augmentRequest(transition, request, out);
mSplitHandler.addEnterOrExitIfNeeded(request, out);
return out;
+ } else if (request.getType() == TRANSIT_PIP
+ && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
+ mActivityEmbeddingController != null)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " Got a PiP-enter request from an Activity Embedding split");
+ mActiveTransitions.add(new MixedTransition(
+ MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
+ // Postpone transition splitting to later.
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ return out;
} else if (request.getRemoteTransition() != null
&& TransitionUtil.isOpeningType(request.getType())
&& (request.getTriggerTask() == null
@@ -355,6 +374,9 @@
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
+ finishTransaction, finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -400,6 +422,58 @@
}
}
+ private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP from an Activity Embedding window");
+ // Split into two transitions (wct)
+ TransitionInfo.Change pipChange = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ }
+ }
+
+ final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT);
+ };
+
+ if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+ // Fallback to dispatching to other handlers.
+ return false;
+ }
+
+ // PIP window should always be on the highest Z order.
+ if (pipChange != null) {
+ mixed.mInFlightSubAnimations = 2;
+ mPipHandler.startEnterAnimation(
+ pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+ finishTransaction,
+ finishCB);
+ } else {
+ mixed.mInFlightSubAnimations = 1;
+ }
+
+ mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
+ startTransaction, finishTransaction, finishCB);
+ return true;
+ }
+
private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -811,6 +885,10 @@
} else {
mPipHandler.end();
}
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.end();
+ mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mPipHandler.end();
if (mixed.mLeftoversHandler != null) {
@@ -851,6 +929,9 @@
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt
index 7940821..c396a20 100644
--- a/libs/hwui/api/current.txt
+++ b/libs/hwui/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.graphics {
public class ColorMatrix {
diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-current.txt
+++ b/libs/hwui/api/module-lib-current.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-removed.txt
+++ b/libs/hwui/api/module-lib-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/removed.txt
+++ b/libs/hwui/api/removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-current.txt
+++ b/libs/hwui/api/system-current.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-removed.txt
+++ b/libs/hwui/api/system-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
index 16c3f2e..2dd67f0 100644
--- a/location/java/android/location/GnssSignalType.java
+++ b/location/java/android/location/GnssSignalType.java
@@ -34,8 +34,7 @@
/**
* Creates a {@link GnssSignalType} with a full list of parameters.
*
- * @param constellationType the constellation type as defined in
- * {@link GnssStatus.ConstellationType}
+ * @param constellationType the constellation type
* @param carrierFrequencyHz the carrier frequency in Hz
* @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
*/
@@ -66,7 +65,7 @@
this.mCodeType = codeType;
}
- /** Returns the {@link GnssStatus.ConstellationType}. */
+ /** Returns the constellation type. */
@GnssStatus.ConstellationType
public int getConstellationType() {
return mConstellationType;
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
index 3c54d4a..b761afa 100644
--- a/media/java/android/media/RingtoneV1.java
+++ b/media/java/android/media/RingtoneV1.java
@@ -16,15 +16,14 @@
package android.media;
+import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources.NotFoundException;
import android.media.audiofx.HapticGenerator;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.RemoteException;
import android.os.Trace;
import android.os.VibrationEffect;
@@ -62,6 +61,7 @@
private final Context mContext;
private final AudioManager mAudioManager;
+ private final Ringtone.Injectables mInjectables;
private VolumeShaper.Configuration mVolumeShaperConfig;
private VolumeShaper mVolumeShaper;
@@ -74,12 +74,10 @@
private final IRingtonePlayer mRemotePlayer;
private final Binder mRemoteToken;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private MediaPlayer mLocalPlayer;
private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
private HapticGenerator mHapticGenerator;
- @UnsupportedAppUsage
private Uri mUri;
private String mTitle;
@@ -94,10 +92,15 @@
private boolean mHapticGeneratorEnabled = false;
private final Object mPlaybackSettingsLock = new Object();
- /** {@hide} */
- @UnsupportedAppUsage
+ /** @hide */
public RingtoneV1(Context context, boolean allowRemote) {
+ this(context, new Ringtone.Injectables(), allowRemote);
+ }
+
+ /** @hide */
+ RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) {
mContext = context;
+ mInjectables = injectables;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAllowRemote = allowRemote;
mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
@@ -200,7 +203,7 @@
}
destroyLocalPlayer();
// try opening uri locally before delegating to remote player
- mLocalPlayer = new MediaPlayer();
+ mLocalPlayer = mInjectables.newMediaPlayer();
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
@@ -240,19 +243,7 @@
*/
public boolean hasHapticChannels() {
// FIXME: support remote player, or internalize haptic channels support and remove entirely.
- try {
- android.os.Trace.beginSection("Ringtone.hasHapticChannels");
- if (mLocalPlayer != null) {
- for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
- if (trackInfo.hasHapticChannels()) {
- return true;
- }
- }
- }
- } finally {
- android.os.Trace.endSection();
- }
- return false;
+ return mInjectables.hasHapticChannels(mLocalPlayer);
}
/**
@@ -334,7 +325,7 @@
* @see android.media.audiofx.HapticGenerator#isAvailable()
*/
public boolean setHapticGeneratorEnabled(boolean enabled) {
- if (!HapticGenerator.isAvailable()) {
+ if (!mInjectables.isHapticGeneratorAvailable()) {
return false;
}
synchronized (mPlaybackSettingsLock) {
@@ -362,7 +353,7 @@
mLocalPlayer.setVolume(mVolume);
mLocalPlayer.setLooping(mIsLooping);
if (mHapticGenerator == null && mHapticGeneratorEnabled) {
- mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+ mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer);
}
if (mHapticGenerator != null) {
mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
@@ -397,7 +388,6 @@
*
* @hide
*/
- @UnsupportedAppUsage
public void setUri(Uri uri) {
setUri(uri, null);
}
@@ -425,7 +415,6 @@
}
/** {@hide} */
- @UnsupportedAppUsage
public Uri getUri() {
return mUri;
}
@@ -556,7 +545,7 @@
Log.e(TAG, "Could not load fallback ringtone");
return false;
}
- mLocalPlayer = new MediaPlayer();
+ mLocalPlayer = mInjectables.newMediaPlayer();
if (afd.getDeclaredLength() < 0) {
mLocalPlayer.setDataSource(afd.getFileDescriptor());
} else {
@@ -594,12 +583,12 @@
}
public boolean isLocalOnly() {
- return mAllowRemote;
+ return !mAllowRemote;
}
public boolean isUsingRemotePlayer() {
// V2 testing api, but this is the v1 approximation.
- return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
+ return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null);
}
class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9a6ed4..9ced2a4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,7 @@
package android.media.audiopolicy;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,6 +49,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.audio.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -417,6 +419,7 @@
* @return {@link AudioManager#SUCCESS} if the update was successful,
* {@link AudioManager#ERROR} otherwise.
*/
+ @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API)
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int updateMixingRules(
@NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) {
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index bbbe7f6..c54bfce 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -16,6 +16,9 @@
package android.media.midi;
+import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
@@ -54,9 +57,11 @@
* android:resource="@xml/device_info" />
* </service></pre>
*/
+@FlaggedApi(FLAG_VIRTUAL_UMP)
public abstract class MidiUmpDeviceService extends Service {
private static final String TAG = "MidiUmpDeviceService";
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
private IMidiManager mMidiManager;
@@ -75,6 +80,7 @@
}
};
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
@Override
public void onCreate() {
mMidiManager = IMidiManager.Stub.asInterface(
@@ -112,6 +118,7 @@
* The number of input and output ports must be equal and non-zero.
* @return list of MidiReceivers
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers();
/**
@@ -120,6 +127,7 @@
* The number of input and output ports must be equal and non-zero.
* @return the list of MidiReceivers
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public final @NonNull List<MidiReceiver> getOutputPortReceivers() {
if (mServer == null) {
return new ArrayList<MidiReceiver>();
@@ -132,6 +140,7 @@
* Returns the {@link MidiDeviceInfo} instance for this service
* @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public final @Nullable MidiDeviceInfo getDeviceInfo() {
return mDeviceInfo;
}
@@ -140,6 +149,7 @@
* Called to notify when the {@link MidiDeviceStatus} has changed
* @param status the current status of the MIDI device
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {
}
@@ -147,9 +157,11 @@
* Called to notify when the virtual MIDI device running in this service has been closed by
* all its clients
*/
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
public void onClose() {
}
+ @FlaggedApi(FLAG_VIRTUAL_UMP)
@Override
public @Nullable IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 80e2247..31e65eb 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -175,5 +175,5 @@
@EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
+ oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
}
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index e886558..7d79a6c 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml
index 132028c..91c92cc1 100644
--- a/media/tests/MediaFrameworkTest/AndroidTest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidTest.xml
@@ -23,5 +23,6 @@
<option name="package" value="com.android.mediaframeworktest" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
+ <option name="isolated-storage" value="false"/>
</test>
</configuration>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 9be7004..30edfa4 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -44,7 +44,6 @@
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
- addMediaMetadataRetrieverStateUnitTests(suite);
addMediaRecorderStateUnitTests(suite);
addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite);
@@ -70,11 +69,6 @@
}
// Running all unit tests checking the state machine may be time-consuming.
- private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
- suite.addTestSuite(MediaMetadataRetrieverTest.class);
- }
-
- // Running all unit tests checking the state machine may be time-consuming.
private void addMediaRecorderStateUnitTests(TestSuite suite) {
suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class);
suite.addTestSuite(MediaRecorderResetStateUnitTest.class);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index bdca474..f70d2d1 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,26 +16,34 @@
package com.android.mediaframeworktest.unit;
+import static org.junit.Assert.assertTrue;
+
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.mediaframeworktest.MediaNames;
import com.android.mediaframeworktest.MediaProfileReader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.FileOutputStream;
import java.io.IOException;
-public class MediaMetadataRetrieverTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MediaMetadataRetrieverTest {
private static final String TAG = "MediaMetadataRetrieverTest";
// Test album art extraction.
@MediumTest
- public static void testGetEmbeddedPicture() throws Exception {
+ @Test
+ public void testGetEmbeddedPicture() throws Exception {
Log.v(TAG, "testGetEmbeddedPicture starts.");
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean supportWMA = MediaProfileReader.getWMAEnable();
@@ -78,7 +86,8 @@
// Test frame capture
@LargeTest
- public static void testThumbnailCapture() throws Exception {
+ @Test
+ public void testThumbnailCapture() throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean supportWMA = MediaProfileReader.getWMAEnable();
boolean supportWMV = MediaProfileReader.getWMVEnable();
@@ -134,7 +143,8 @@
}
@LargeTest
- public static void testMetadataRetrieval() throws Exception {
+ @Test
+ public void testMetadataRetrieval() throws Exception {
boolean supportWMA = MediaProfileReader.getWMAEnable();
boolean supportWMV = MediaProfileReader.getWMVEnable();
boolean hasFailed = false;
@@ -169,7 +179,8 @@
// If the specified call order and valid media file is used, no exception
// should be thrown.
@MediumTest
- public static void testBasicNormalMethodCallSequence() throws Exception {
+ @Test
+ public void testBasicNormalMethodCallSequence() throws Exception {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
@@ -197,7 +208,8 @@
// If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must
// return null.
@MediumTest
- public static void testBasicAbnormalMethodCallSequence() {
+ @Test
+ public void testBasicAbnormalMethodCallSequence() {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) {
@@ -213,7 +225,8 @@
// Test setDataSource()
@MediumTest
- public static void testSetDataSource() throws IOException {
+ @Test
+ public void testSetDataSource() throws IOException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean hasFailed = false;
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
index 55b98c4..8d1e5e3 100644
--- a/media/tests/ringtone/Android.bp
+++ b/media/tests/ringtone/Android.bp
@@ -9,15 +9,24 @@
srcs: ["src/**/*.java"],
libs: [
- "android.test.runner",
"android.test.base",
+ "android.test.mock",
+ "android.test.runner",
],
static_libs: [
- "androidx.test.rules",
- "testng",
+ "androidx.test.ext.junit",
"androidx.test.ext.truth",
+ "androidx.test.rules",
"frameworks-base-testutils",
+ "mockito-target-inline-minus-junit4",
+ "testables",
+ "testng",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
],
test_suites: [
diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS
new file mode 100644
index 0000000..93b44f4
--- /dev/null
+++ b/media/tests/ringtone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345036
+
+include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
similarity index 69%
rename from media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
rename to media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
index 3c0c684..2c8daba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
+++ b/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-package com.android.mediaframeworktest.unit;
+package com.android.media;
import static android.media.Ringtone.MEDIA_SOUND;
import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
import static android.media.Ringtone.MEDIA_VIBRATION;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted;
+import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped;
+
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
@@ -53,34 +55,29 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.testing.TestableContext;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.mediaframeworktest.R;
+import com.android.framework.base.media.ringtone.tests.R;
+import com.android.media.testing.RingtoneInjectablesTrackingTestRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.FileNotFoundException;
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
+/**
+ * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}.
+ */
@RunWith(AndroidJUnit4.class)
-public class RingtoneTest {
+public class RingtoneBuilderTest {
private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
@@ -93,11 +90,8 @@
private static final VibrationEffect VIBRATION_EFFECT =
VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
- private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
- VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
- @Rule
- public final RingtoneInjectablesTrackingTestRule
+ @Rule public final RingtoneInjectablesTrackingTestRule
mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
@Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
@@ -122,6 +116,7 @@
mContext = spy(testContext);
}
+
@Test
public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
@@ -142,14 +137,14 @@
assertThat(ringtone.isLocalOnly()).isFalse();
// Prepare
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Verify dynamic controls.
ringtone.setVolume(0.8f);
@@ -165,7 +160,7 @@
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
// playback case. This shouldn't be necessary in other tests that have the same basic
@@ -199,16 +194,16 @@
assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
// Prepare
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verifyZeroInteractions(mMockRemotePlayer);
verifyZeroInteractions(mMockVibrator);
@@ -220,8 +215,8 @@
setupFileNotFound(mockMediaPlayer, SOUND_URI);
Ringtone ringtone =
newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(SOUND_URI)
- .build();
+ .setUri(SOUND_URI)
+ .build();
assertThat(ringtone).isNotNull();
assertThat(ringtone.isUsingRemotePlayer()).isTrue();
@@ -284,7 +279,7 @@
// Prepare
// Uses attributes with haptic channels enabled, but will use the effect when there aren't
// any present.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -292,7 +287,7 @@
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
// Verify dynamic controls.
@@ -310,7 +305,7 @@
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
@@ -388,7 +383,7 @@
// Prepare
// Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
// knows there aren't any.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -443,7 +438,7 @@
// Prepare
// Uses attributes with haptic channels enabled, but will use the effect when there aren't
// any present.
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).setVolume(0.0f); // Vibration-only: sound muted.
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
@@ -451,7 +446,7 @@
// Play
ringtone.play();
// Vibrator.vibrate isn't called because the vibration comes from the sound.
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Verify dynamic controls (no-op without sound)
ringtone.setVolume(0.8f);
@@ -466,7 +461,7 @@
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
// This test is intended to strictly verify all interactions with MediaPlayer in a local
// playback case. This shouldn't be necessary in other tests that have the same basic
@@ -496,17 +491,17 @@
// Prepare
// The attributes here have haptic channels enabled (unlike above)
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
when(mockMediaPlayer.isPlaying()).thenReturn(true);
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verifyZeroInteractions(mMockRemotePlayer);
// Nothing after the initial hasVibrator - it uses audio-coupled.
@@ -536,7 +531,7 @@
// Prepare
// The attributes here have haptic channels enabled (unlike above)
- verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+ verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
verify(mockMediaPlayer).prepare();
// Play
@@ -559,7 +554,7 @@
@Test
public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
AssetFileDescriptor testResourceFd =
- mContext.getResources().openRawResourceFd(R.raw.shortmp3);
+ mContext.getResources().openRawResourceFd(R.raw.test_sound_file);
// Ensure it will flow as expected.
assertThat(testResourceFd).isNotNull();
assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
@@ -575,18 +570,18 @@
// Delegates straight to fallback in local player.
// Prepare
- verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
+ verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
verify(mockMediaPlayer).setVolume(1.0f);
verify(mockMediaPlayer).setLooping(false);
verify(mockMediaPlayer).prepare();
// Play
ringtone.play();
- verifyLocalPlay(mockMediaPlayer);
+ verifyPlayerStarted(mockMediaPlayer);
// Release
ringtone.stop();
- verifyLocalStop(mockMediaPlayer);
+ verifyPlayerStopped(mockMediaPlayer);
verifyNoMoreInteractions(mockMediaPlayer);
verifyNoMoreInteractions(mMockRemotePlayer);
@@ -615,24 +610,10 @@
verifyNoMoreInteractions(mMockRemotePlayer);
}
- @Test
- public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
- mContext.getOrCreateTestableResources()
- .addOverride(com.android.internal.R.raw.fallbackring, null);
- Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
- .setUri(null)
- .setLocalOnly()
- .build();
- // Local player fallback fails as the resource isn't found (no media player creation is
- // attempted), and since there is no local player, the ringtone ends up having nothing to
- // do.
- assertThat(ringtone).isNull();
- }
-
private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
AudioAttributes audioAttributes) {
return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
- .setInjectables(mMediaPlayerRule.injectables);
+ .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables());
}
private static AudioAttributes audioAttributes(int audioUsage) {
@@ -647,194 +628,4 @@
doThrow(new FileNotFoundException("Fake file not found"))
.when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
}
-
- private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
- AudioAttributes expectedAudioAttributes) throws Exception {
- verify(mockPlayer).setDataSource(mContext, expectedUri);
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
- AudioAttributes expectedAudioAttributes) throws Exception {
- // This is very specific but it's a simple way to test that the test resource matches.
- if (afd.getDeclaredLength() < 0) {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor());
- } else {
- verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
- afd.getStartOffset(),
- afd.getDeclaredLength());
- }
- verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
- verify(mockPlayer).setPreferredDevice(null);
- verify(mockPlayer).prepare();
- }
-
- private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).setOnCompletionListener(any());
- verify(mockMediaPlayer).start();
- }
-
- private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
- verify(mockMediaPlayer).stop();
- verify(mockMediaPlayer).setOnCompletionListener(isNull());
- verify(mockMediaPlayer).reset();
- verify(mockMediaPlayer).release();
- }
-
- /**
- * This rule ensures that all expected media player creations from the factory do actually
- * occur. The reason for this level of control is that creating a media player is fairly
- * expensive and blocking, so we do want unit tests of this class to "declare" interactions
- * of all created media players.
- *
- * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
- * failed (and media player assertions may just be a distracting side effect). Otherwise, the
- * teardown failures hide the real test ones.
- */
- public static class RingtoneInjectablesTrackingTestRule implements TestRule {
- public Ringtone.Injectables injectables = new TestInjectables();
- public boolean hapticGeneratorAvailable = true;
-
- // Queue of (local) media players, in order of expected creation. Enqueue using
- // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
- // This queue is asserted to be empty at the end of the test.
- private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
- // Similar to media players, but for haptic generator, which also needs releasing.
- private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
- // Media players with haptic channels.
- private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- base.evaluate();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
- .that(mMockMediaPlayerQueue).isEmpty();
- // Only assert if the test didn't fail (base.evaluate() would throw).
- assertWithMessage(
- "Test setup an expectLocalHapticGenerator but it wasn't consumed")
- .that(mMockHapticGeneratorMap).isEmpty();
- }
- };
- }
-
- private TestMediaPlayer expectLocalMediaPlayer() {
- TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
- // Delegate to simulated methods. This means they can be verified but also reflect
- // realistic transitions from the TestMediaPlayer.
- doCallRealMethod().when(mockMediaPlayer).start();
- doCallRealMethod().when(mockMediaPlayer).stop();
- doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- when(mockMediaPlayer.isLooping()).thenCallRealMethod();
- mMockMediaPlayerQueue.add(mockMediaPlayer);
- return mockMediaPlayer;
- }
-
- private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
- HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
- // A test should never want this.
- assertWithMessage("Can't expect a second haptic generator created "
- + "for one media player")
- .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
- .isNull();
- return mockHapticGenerator;
- }
-
- private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
- if (hasHapticChannels) {
- mHapticChannels.add(mp);
- } else {
- mHapticChannels.remove(mp);
- }
- }
-
- private class TestInjectables extends Ringtone.Injectables {
- @Override
- public MediaPlayer newMediaPlayer() {
- assertWithMessage(
- "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
- .that(mMockMediaPlayerQueue)
- .isNotEmpty();
- return mMockMediaPlayerQueue.remove();
- }
-
- @Override
- public boolean isHapticGeneratorAvailable() {
- return hapticGeneratorAvailable;
- }
-
- @Override
- public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
- HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
- assertWithMessage("Unexpected HapticGenerator creation. "
- + "Bug or need expectHapticGenerator")
- .that(mockHapticGenerator)
- .isNotNull();
- return mockHapticGenerator;
- }
-
- @Override
- public boolean isHapticPlaybackSupported() {
- return true;
- }
-
- @Override
- public boolean hasHapticChannels(MediaPlayer mp) {
- return mHapticChannels.contains(mp);
- }
- }
- }
-
- /**
- * MediaPlayer relies on a native backend and so its necessary to intercept calls from
- * fake usage hitting them.
- *
- * Mocks don't work directly on native calls, but if they're overridden then it does work.
- * Some basic state faking is also done to make the mocks more realistic.
- */
- private static class TestMediaPlayer extends MediaPlayer {
- private boolean mIsPlaying = false;
- private boolean mIsLooping = false;
-
- @Override
- public void start() {
- mIsPlaying = true;
- }
-
- @Override
- public void stop() {
- mIsPlaying = false;
- }
-
- @Override
- public void setLooping(boolean value) {
- mIsLooping = value;
- }
-
- @Override
- public boolean isLooping() {
- return mIsLooping;
- }
-
- @Override
- public boolean isPlaying() {
- return mIsPlaying;
- }
-
- void simulatePlayingFinished() {
- if (!mIsPlaying) {
- throw new IllegalStateException(
- "Attempted to pretend playing finished when not playing");
- }
- mIsPlaying = false;
- }
- }
}
diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
new file mode 100644
index 0000000..e97e117
--- /dev/null
+++ b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 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.media.testing;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.MediaPlayer;
+import android.net.Uri;
+
+/**
+ * Helper class with assertion methods on mock {@link MediaPlayer} instances.
+ */
+public final class MediaPlayerTestHelper {
+
+ /** Verify this local media player mock instance was started. */
+ public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) {
+ verify(mockMediaPlayer).setOnCompletionListener(any());
+ verify(mockMediaPlayer).start();
+ }
+
+ /** Verify this local media player mock instance was stopped and released. */
+ public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) {
+ verify(mockMediaPlayer).stop();
+ verify(mockMediaPlayer).setOnCompletionListener(isNull());
+ verify(mockMediaPlayer).reset();
+ verify(mockMediaPlayer).release();
+ }
+
+ /** Verify this local media player mock instance was setup with given attributes. */
+ public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer,
+ Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception {
+ verify(mockPlayer).setDataSource(context, expectedUri);
+ verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+ verify(mockPlayer).setPreferredDevice(null);
+ verify(mockPlayer).prepare();
+ }
+
+ /** Verify this local media player mock instance was setup with given fallback attributes. */
+ public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer,
+ AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception {
+ // This is very specific but it's a simple way to test that the test resource matches.
+ if (afd.getDeclaredLength() < 0) {
+ verify(mockPlayer).setDataSource(afd.getFileDescriptor());
+ } else {
+ verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength());
+ }
+ verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+ verify(mockPlayer).setPreferredDevice(null);
+ verify(mockPlayer).prepare();
+ }
+
+ private MediaPlayerTestHelper() {
+ }
+}
diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
new file mode 100644
index 0000000..25752ce
--- /dev/null
+++ b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 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.media.testing;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.when;
+
+import android.media.MediaPlayer;
+import android.media.Ringtone;
+import android.media.audiofx.HapticGenerator;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+
+import java.util.ArrayDeque;
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * This rule ensures that all expected media player creations from the factory do actually
+ * occur. The reason for this level of control is that creating a media player is fairly
+ * expensive and blocking, so we do want unit tests of this class to "declare" interactions
+ * of all created media players.
+ * <p>
+ * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
+ * failed (and media player assertions may just be a distracting side effect). Otherwise, the
+ * teardown failures hide the real test ones.
+ */
+public class RingtoneInjectablesTrackingTestRule implements TestRule {
+
+ private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables();
+
+ // Queue of (local) media players, in order of expected creation. Enqueue using
+ // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
+ // This queue is asserted to be empty at the end of the test.
+ private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
+
+ // Similar to media players, but for haptic generator, which also needs releasing.
+ private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
+
+ // Media players with haptic channels.
+ private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
+
+ private boolean mHapticGeneratorAvailable = true;
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ // Only assert if the test didn't fail (base.evaluate() would throw).
+ assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
+ .that(mMockMediaPlayerQueue).isEmpty();
+ // Only assert if the test didn't fail (base.evaluate() would throw).
+ assertWithMessage(
+ "Test setup an expectLocalHapticGenerator but it wasn't consumed")
+ .that(mMockHapticGeneratorMap).isEmpty();
+ }
+ };
+ }
+
+ /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */
+ public Ringtone.Injectables getRingtoneTestInjectables() {
+ return mRingtoneTestInjectables;
+ }
+
+ /**
+ * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance
+ * created with {@link #getRingtoneTestInjectables()}.
+ *
+ * <p>If a media player is not created during the test execution after this method is called
+ * then the test will fail. It will also fail if the ringtone attempts to create one without
+ * this method being called first.
+ */
+ public TestMediaPlayer expectLocalMediaPlayer() {
+ TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
+ // Delegate to simulated methods. This means they can be verified but also reflect
+ // realistic transitions from the TestMediaPlayer.
+ doCallRealMethod().when(mockMediaPlayer).start();
+ doCallRealMethod().when(mockMediaPlayer).stop();
+ doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
+ when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+ mMockMediaPlayerQueue.add(mockMediaPlayer);
+ return mockMediaPlayer;
+ }
+
+ /**
+ * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance
+ * created with {@link #getRingtoneTestInjectables()}.
+ *
+ * <p>If a haptic generator is not created during the test execution after this method is called
+ * then the test will fail. It will also fail if the ringtone attempts to create one without
+ * this method being called first.
+ */
+ public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) {
+ HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
+ // A test should never want this.
+ assertWithMessage("Can't expect a second haptic generator created "
+ + "for one media player")
+ .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator))
+ .isNull();
+ return mockHapticGenerator;
+ }
+
+ /**
+ * Configures the {@link MediaPlayer} to always return given flag when
+ * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called.
+ */
+ public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
+ if (hasHapticChannels) {
+ mHapticChannels.add(mp);
+ } else {
+ mHapticChannels.remove(mp);
+ }
+ }
+
+ /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */
+ private class TestInjectables extends Ringtone.Injectables {
+ @Override
+ public MediaPlayer newMediaPlayer() {
+ assertWithMessage(
+ "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
+ .that(mMockMediaPlayerQueue)
+ .isNotEmpty();
+ return mMockMediaPlayerQueue.remove();
+ }
+
+ @Override
+ public boolean isHapticGeneratorAvailable() {
+ return mHapticGeneratorAvailable;
+ }
+
+ @Override
+ public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
+ HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
+ assertWithMessage("Unexpected HapticGenerator creation. "
+ + "Bug or need expectHapticGenerator")
+ .that(mockHapticGenerator)
+ .isNotNull();
+ return mockHapticGenerator;
+ }
+
+ @Override
+ public boolean isHapticPlaybackSupported() {
+ return true;
+ }
+
+ @Override
+ public boolean hasHapticChannels(MediaPlayer mp) {
+ return mHapticChannels.contains(mp);
+ }
+ }
+
+ /**
+ * MediaPlayer relies on a native backend and so its necessary to intercept calls from
+ * fake usage hitting them.
+ * <p>
+ * Mocks don't work directly on native calls, but if they're overridden then it does work.
+ * Some basic state faking is also done to make the mocks more realistic.
+ */
+ public static class TestMediaPlayer extends MediaPlayer {
+ private boolean mIsPlaying = false;
+ private boolean mIsLooping = false;
+
+ @Override
+ public void start() {
+ mIsPlaying = true;
+ }
+
+ @Override
+ public void stop() {
+ mIsPlaying = false;
+ }
+
+ @Override
+ public void setLooping(boolean value) {
+ mIsLooping = value;
+ }
+
+ @Override
+ public boolean isLooping() {
+ return mIsLooping;
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mIsPlaying;
+ }
+
+ /**
+ * Updates {@link #isPlaying()} result to false, if it's set to true.
+ *
+ * @throws IllegalStateException is {@link #isPlaying()} is already false
+ */
+ public void simulatePlayingFinished() {
+ if (!mIsPlaying) {
+ throw new IllegalStateException(
+ "Attempted to pretend playing finished when not playing");
+ }
+ mIsPlaying = false;
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 1cc2867..0b7a568 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -26,7 +26,7 @@
}
allprojects {
- extra["jetpackComposeVersion"] = "1.6.0-alpha02"
+ extra["jetpackComposeVersion"] = "1.6.0-alpha07"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
similarity index 96%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index df1d7d1..b001cad 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -18,8 +18,8 @@
import android.os.Bundle
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material.icons.outlined.Delete
-import androidx.compose.material.icons.outlined.Launch
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
@@ -47,7 +47,7 @@
override fun Page(arguments: Bundle?) {
RegularScaffold(title = TITLE) {
val actionButtons = listOf(
- ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},
ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
)
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 8b56336..aafae5f 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
#
[versions]
-agp = "8.1.1"
+agp = "8.1.2"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
kotlin = "1.9.0"
-truth = "1.1"
+truth = "1.1.5"
[libraries]
dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index da04f42..ce89de6 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index b810511..b73bbd8 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-alpha04")
+ api("androidx.compose.material3:material3:1.2.0-alpha09")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.7.1")
+ api("androidx.navigation:navigation-compose:2.7.4")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 50243dc..cce8235 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -41,7 +41,7 @@
api("androidx.arch.core:core-testing:2.2.0-alpha01")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
- api("org.mockito.kotlin:mockito-kotlin:5.1.0")
+ api("org.mockito.kotlin:mockito-kotlin:2.2.11")
api("org.mockito:mockito-core") {
version {
strictly("2.28.2")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index f54de15..09cb98e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -22,6 +22,8 @@
import android.os.UserManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -32,15 +34,15 @@
import com.android.settingslib.widget.restricted.R
data class Restrictions(
- val userId: Int,
+ val userId: Int = UserHandle.myUserId(),
val keys: List<String>,
)
sealed interface RestrictedMode
-object NoRestricted : RestrictedMode
+data object NoRestricted : RestrictedMode
-object BaseUserRestricted : RestrictedMode
+data object BaseUserRestricted : RestrictedMode
interface BlockedByAdmin : RestrictedMode {
fun getSummary(checked: Boolean?): String
@@ -79,6 +81,17 @@
typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
+@Composable
+internal fun RestrictionsProviderFactory.rememberRestrictedMode(
+ restrictions: Restrictions,
+): State<RestrictedMode?> {
+ val context = LocalContext.current
+ val restrictionsProvider = remember(restrictions) {
+ this(context, restrictions)
+ }
+ return restrictionsProvider.restrictedModeState()
+}
+
internal class RestrictionsProviderImpl(
private val context: Context,
private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 1fa854a..17e9708 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -45,6 +45,7 @@
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.flow.Flow
@@ -149,14 +150,13 @@
@Composable
fun getSummary(record: T): State<String> {
- val restrictionsProvider = remember(record.app.userId) {
- val restrictions = Restrictions(
+ val restrictions = remember(record.app.userId) {
+ Restrictions(
userId = record.app.userId,
keys = listModel.switchRestrictionKeys,
)
- restrictionsProviderFactory(context, restrictions)
}
- val restrictedMode = restrictionsProvider.restrictedModeState()
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)
val allowed = listModel.isAllowed(record)
return remember {
derivedStateOf {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
new file mode 100644
index 0000000..50490c0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+
+@Composable
+fun RestrictedPreference(
+ model: PreferenceModel,
+ restrictions: Restrictions,
+) {
+ RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedPreference(
+ model: PreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+ if (restrictions.keys.isEmpty()) {
+ Preference(model)
+ return
+ }
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ val restrictedSwitchModel = remember(restrictedMode) {
+ RestrictedPreferenceModel(model, restrictedMode)
+ }
+ restrictedSwitchModel.RestrictionWrapper {
+ Preference(restrictedSwitchModel)
+ }
+}
+
+private class RestrictedPreferenceModel(
+ model: PreferenceModel,
+ private val restrictedMode: RestrictedMode?,
+) : PreferenceModel {
+ override val title = model.title
+ override val summary = model.summary
+ override val icon = model.icon
+
+ override val enabled = when (restrictedMode) {
+ NoRestricted -> model.enabled
+ else -> stateOf(false)
+ }
+
+ override val onClick = when (restrictedMode) {
+ NoRestricted -> model.onClick
+ // Need to passthrough onClick for clickable semantics, although since enabled is false so
+ // this will not be called.
+ BaseUserRestricted -> model.onClick
+ else -> null
+ }
+
+ @Composable
+ fun RestrictionWrapper(content: @Composable () -> Unit) {
+ if (restrictedMode !is BlockedByAdmin) {
+ content()
+ return
+ }
+ Box(
+ Modifier
+ .clickable(
+ role = Role.Button,
+ onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() },
+ )
+ ) { content() }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index e77dcd4..2129403 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spaprivileged.template.preference
import android.content.Context
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@@ -40,22 +41,29 @@
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@Composable
fun RestrictedSwitchPreference(
model: SwitchPreferenceModel,
restrictions: Restrictions,
- restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+) {
+ RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl)
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedSwitchPreference(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
) {
if (restrictions.keys.isEmpty()) {
SwitchPreference(model)
return
}
val context = LocalContext.current
- val restrictionsProvider = remember(restrictions) {
- restrictionsProviderFactory(context, restrictions)
- }
- val restrictedMode = restrictionsProvider.restrictedModeState().value
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
val restrictedSwitchModel = remember(restrictedMode) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
@@ -112,8 +120,8 @@
override val onCheckedChange = when (restrictedMode) {
null -> null
is NoRestricted -> model.onCheckedChange
- // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although
- // since changeable is false this will not be called.
+ // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
+ // is false so this will not be called.
is BaseUserRestricted -> model.onCheckedChange
// Pass null since semantics ToggleableState is provided in RestrictionWrapper.
is BlockedByAdmin -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 86b6f02..f9abefc 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,15 +16,15 @@
package com.android.settingslib.spaprivileged.template.scaffold
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
@Composable
fun MoreOptionsScope.RestrictedMenuItem(
@@ -35,6 +35,7 @@
RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)
}
+@VisibleForTesting
@Composable
internal fun MoreOptionsScope.RestrictedMenuItemImpl(
text: String,
@@ -42,12 +43,8 @@
onClick: () -> Unit,
restrictionsProviderFactory: RestrictionsProviderFactory,
) {
- val context = LocalContext.current
- val restrictionsProvider = remember(restrictions) {
- restrictionsProviderFactory(context, restrictions)
- }
- val restrictedMode = restrictionsProvider.restrictedModeState().value
- MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) {
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {
when (restrictedMode) {
is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()
else -> onClick()
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
new file mode 100644
index 0000000..eadf0ca
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.settingslib.spaprivileged.template.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private var clicked = false
+
+ private val preferenceModel = object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = { clicked = true }
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_clickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
+ @Test
+ fun whenNoRestricted_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenNoRestricted_clickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_notClickable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(clicked).isFalse()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ private fun setContent(restrictions: Restrictions) {
+ composeTestRule.setContent {
+ RestrictedPreference(preferenceModel, restrictions) { _, _ ->
+ fakeRestrictionsProvider
+ }
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val USER_ID = 0
+ const val RESTRICTION_KEY = "restriction_key"
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java
new file mode 100644
index 0000000..a9fd380
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java
@@ -0,0 +1,46 @@
+/*
+ * 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.settingslib.testutils.shadow;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.hardware.display.ColorDisplayManager;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(ColorDisplayManager.class)
+public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager {
+
+ private boolean mIsReduceBrightColorsActivated;
+
+ @Implementation
+ @SystemApi
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ public boolean setReduceBrightColorsActivated(boolean activated) {
+ mIsReduceBrightColorsActivated = activated;
+ return true;
+ }
+
+ @Implementation
+ @SystemApi
+ public boolean isReduceBrightColorsActivated() {
+ return mIsReduceBrightColorsActivated;
+ }
+
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4954cb4..9d3200d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -243,9 +243,6 @@
"tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
- /* Log fakes */
- "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt",
-
/* QS fakes */
"tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
],
@@ -437,6 +434,7 @@
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "flag-junit-base",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 0623d4a..03f7c99 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -144,9 +144,32 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
"exclude-annotation": "android.platform.test.annotations.Postsubmit"
}
]
}
+ ],
+ // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged
+ "sysui-screenshot-test-staged": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 3d670b8..c6e429a 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,6 +22,7 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -66,6 +67,7 @@
override fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View {
throwComposeUnavailableError()
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 7b11ac7..1722685 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -31,6 +31,7 @@
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.communal.ui.compose.CommunalHub
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -96,8 +97,11 @@
override fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View {
- return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } }
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
+ }
}
// TODO(b/298525212): remove once Compose exposes window inset bounds.
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 796abf4b..16c2437 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,6 +31,7 @@
],
static_libs: [
+ "CommunalLayoutLib",
"SystemUI-core",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 4d2978d..3d827fb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -3,20 +3,63 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
+import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.integerResource
+import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
+import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
@Composable
-fun CommunalHub(modifier: Modifier = Modifier) {
+fun CommunalHub(
+ modifier: Modifier = Modifier,
+ viewModel: CommunalViewModel,
+) {
+ val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
Box(
modifier = modifier.fillMaxSize().background(Color.White),
) {
- Text(
- modifier = Modifier.align(Alignment.Center),
- text = "Hello Communal!",
+ CommunalGridLayout(
+ modifier = Modifier.align(Alignment.CenterStart),
+ layoutConfig =
+ CommunalGridLayoutConfig(
+ gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size),
+ gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size),
+ gridHeight = dimensionResource(R.dimen.communal_grid_height),
+ gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
+ ),
+ communalCards = if (showTutorial) tutorialContent else emptyList(),
)
}
}
+
+private val tutorialContent =
+ listOf(
+ tutorialCard(CommunalGridLayoutCard.Size.FULL),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.THIRD),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ tutorialCard(CommunalGridLayoutCard.Size.HALF),
+ )
+
+private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
+ return object : CommunalGridLayoutCard() {
+ override val supportedSizes = listOf(size)
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ Card(modifier = modifier, content = {})
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index d1c12ac..f3bef7b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -19,6 +19,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -32,7 +33,11 @@
/** The communal scene shows glanceable hub when the device is locked and docked. */
@SysUISingleton
-class CommunalScene @Inject constructor() : ComposableScene {
+class CommunalScene
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) : ComposableScene {
override val key = SceneKey.Communal
override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
@@ -45,6 +50,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- CommunalHub(modifier)
+ CommunalHub(modifier, viewModel)
}
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 75e71e4..9ac1e9f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -730,6 +730,9 @@
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
+ <!-- Component names of allowed communal widgets -->
+ <string-array name="config_communalWidgetAllowlist" translatable="false" />
+
<!-- Component name of communal source service -->
<string name="config_communalSourceComponent" translatable="false">@null</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1b09c6a..10fd8b5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -766,6 +766,8 @@
<dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+ <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
+ <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -1667,6 +1669,15 @@
<!-- Height percentage of the parent container occupied by the communal view -->
<item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+ <!-- Size of each communal grid column -->
+ <dimen name="communal_grid_column_size">64dp</dimen>
+ <!-- Size of each communal grid gutter between columns -->
+ <dimen name="communal_grid_gutter_size">16dp</dimen>
+ <!-- Height of the communal grid layout -->
+ <dimen name="communal_grid_height">630dp</dimen>
+ <!-- Number of columns for each communal card -->
+ <integer name="communal_grid_columns_per_card">6</integer>
+
<dimen name="drag_and_drop_icon_size">70dp</dimen>
<dimen name="qs_tile_service_request_dialog_width">304dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7e44adf..7a6d29a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -209,6 +209,8 @@
<string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
<string name="screenshot_failed_title">Couldn\'t save screenshot</string>
+ <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] -->
+ <string name="screenshot_failed_external_display_indication">External Display</string>
<!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
<string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
<!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 01a75d9..e47d36f 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,8 @@
*/
package com.android.keyguard
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -37,7 +39,7 @@
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
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -61,6 +63,7 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import java.util.Locale
import java.util.TimeZone
@@ -297,7 +300,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
- if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
if (!isKeyguardVisible) {
clock?.run {
smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -342,9 +345,9 @@
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
disposableHandle =
parent.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
- if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
} else {
@@ -454,8 +457,9 @@
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToAodTransition
- .filter { it.transitionState == TransitionState.FINISHED }
+ keyguardTransitionInteractor.transitionStepsToState(AOD)
+ .filter { it.transitionState == TransitionState.STARTED }
+ .filter { it.from != LOCKSCREEN }
.collect { handleDoze(1f) }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 79642bd..758d1fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -54,6 +54,9 @@
import com.android.systemui.flags.FeatureFlags;
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.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -102,10 +105,11 @@
private final Rect mClipBounds = new Rect();
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
-
+ private boolean mGoneToAodTransitionRunning = false;
private DumpManager mDumpManager;
private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -135,6 +139,7 @@
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
PowerInteractor powerInteractor) {
super(keyguardStatusView);
@@ -144,12 +149,13 @@
mConfigurationController = configurationController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
- logger.getBuffer());
+ featureFlags, logger.getBuffer());
mInteractionJankMonitor = interactionJankMonitor;
mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -199,6 +205,15 @@
dozeTimeTick();
}
}, context);
+
+ collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+ (TransitionStep step) -> {
+ if (step.getTransitionState() == TransitionState.RUNNING) {
+ mGoneToAodTransitionRunning = true;
+ } else {
+ mGoneToAodTransitionRunning = false;
+ }
+ }, context);
}
public KeyguardStatusView getView() {
@@ -266,7 +281,7 @@
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
mView.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 84e06e2..205c297 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3463,7 +3463,8 @@
@Deprecated
private boolean isUnlockWithFacePossible(int userId) {
if (isFaceAuthInteractorEnabled()) {
- return getFaceAuthInteractor().canFaceAuthRun();
+ return getFaceAuthInteractor() != null
+ && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();
}
return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index c64ae01..d524e4a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,6 +23,8 @@
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.StatusBarState;
@@ -53,6 +55,7 @@
private boolean mKeyguardViewVisibilityAnimating;
private boolean mLastOccludedState = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
+ private final FeatureFlags mFeatureFlags;
private final LogBuffer mLogBuffer;
public KeyguardVisibilityHelper(View view,
@@ -60,12 +63,14 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
boolean animateYPos,
+ FeatureFlags featureFlags,
LogBuffer logBuffer) {
mView = view;
mKeyguardStateController = keyguardStateController;
mDozeParameters = dozeParameters;
mScreenOffAnimationController = screenOffAnimationController;
mAnimateYPos = animateYPos;
+ mFeatureFlags = featureFlags;
mLogBuffer = logBuffer;
}
@@ -162,13 +167,17 @@
animProps,
true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
- log("ScreenOff transition");
- mKeyguardViewVisibilityAnimating = true;
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ log("Using GoneToAodTransition");
+ mKeyguardViewVisibilityAnimating = false;
+ } else {
+ log("ScreenOff transition");
+ mKeyguardViewVisibilityAnimating = true;
- // Ask the screen off animation controller to animate the keyguard visibility for us
- // since it may need to be cancelled due to keyguard lifecycle events.
- mScreenOffAnimationController.animateInKeyguard(
- mView, mSetVisibleEndRunnable);
+ // Ask the screen off animation controller to animate the keyguard visibility
+ // for us since it may need to be cancelled due to keyguard lifecycle events.
+ mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable);
+ }
} else {
log("Direct set Visibility to VISIBLE");
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
new file mode 100644
index 0000000..b8e2de4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.communal.dagger
+
+import com.android.systemui.communal.data.repository.CommunalRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ CommunalRepositoryModule::class,
+ CommunalTutorialRepositoryModule::class,
+ CommunalWidgetRepositoryModule::class,
+ ]
+)
+class CommunalModule
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
new file mode 100644
index 0000000..f9c4f29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.communal.data.model
+
+import com.android.systemui.communal.shared.CommunalContentSize
+
+/** Metadata for the default widgets */
+data class CommunalWidgetMetadata(
+ /* Widget provider component name */
+ val componentName: String,
+
+ /* Defines the order in which the widget will be rendered in the grid. */
+ val priority: Int,
+
+ /* Supported sizes */
+ val sizes: List<CommunalContentSize>
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e2a7d07..f13b62f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -27,13 +27,17 @@
import android.os.UserManager
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.CommunalContentSize
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -45,15 +49,20 @@
interface CommunalWidgetRepository {
/** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */
val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?>
+
+ /** Widgets that are allowed to render in the glanceable hub */
+ val communalWidgetAllowlist: List<CommunalWidgetMetadata>
}
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
+ @Application private val applicationContext: Context,
private val appWidgetManager: AppWidgetManager,
private val appWidgetHost: AppWidgetHost,
broadcastDispatcher: BroadcastDispatcher,
+ communalRepository: CommunalRepository,
private val packageManager: PackageManager,
private val userManager: UserManager,
private val userTracker: UserTracker,
@@ -64,12 +73,18 @@
const val TAG = "CommunalWidgetRepository"
const val WIDGET_LABEL = "Stopwatch"
}
+ override val communalWidgetAllowlist: List<CommunalWidgetMetadata>
private val logger = Logger(logBuffer, TAG)
// Whether the [AppWidgetHost] is listening for updates.
private var isHostListening = false
+ init {
+ communalWidgetAllowlist =
+ if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList()
+ }
+
// Widgets that should be rendered in communal mode.
private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
@@ -129,6 +144,18 @@
return@map addWidget(providerInfo)
}
+ private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
+ val componentNames =
+ applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
+ return componentNames.mapIndexed { index, name ->
+ CommunalWidgetMetadata(
+ componentName = name,
+ priority = componentNames.size - index,
+ sizes = listOf(CommunalContentSize.HALF)
+ )
+ }
+ }
+
private fun startListening() {
if (isHostListening) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
new file mode 100644
index 0000000..0bd7d86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
@@ -0,0 +1,8 @@
+package com.android.systemui.communal.shared
+
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+ FULL,
+ HALF,
+ THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
index 932dbfb..ad02f62 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
@@ -2,6 +2,7 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.removeView
@@ -9,14 +10,20 @@
import javax.inject.Inject
/** A keyguard section that hosts the communal hub. */
-class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() {
+class DefaultCommunalHubSection
+@Inject
+constructor(
+ private val viewModel: CommunalViewModel,
+) : KeyguardSection() {
private val communalHubViewId = R.id.communal_hub
override fun addViews(constraintLayout: ConstraintLayout) {
constraintLayout.addView(
- ComposeFacade.createCommunalView(constraintLayout.context).apply {
- id = communalHubViewId
- },
+ ComposeFacade.createCommunalView(
+ context = constraintLayout.context,
+ viewModel = viewModel,
+ )
+ .apply { id = communalHubViewId },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
new file mode 100644
index 0000000..ddeb1d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class CommunalViewModel
+@Inject
+constructor(
+ tutorialInteractor: CommunalTutorialInteractor,
+) {
+ /** Whether communal hub should show tutorial content. */
+ val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 5c1539a..9221832b 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -22,6 +22,7 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -73,8 +74,9 @@
sceneByKey: Map<SceneKey, Scene>,
): View
- /** Create a [View] that represents the communal hub. */
+ /** Create a [View] to represent [viewModel] on screen. */
fun createCommunalView(
context: Context,
+ viewModel: CommunalViewModel,
): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7d4e1a1..f0d7592 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -42,6 +42,7 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
@@ -170,6 +171,7 @@
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommonRepositoryModule.class,
+ CommunalModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b7c3662..081618e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -503,10 +503,9 @@
@Keep
@JvmField
val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- unreleasedFlag(
- name = "record_task_content",
+ releasedFlag(
+ name = "enable_record_task_content",
namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- teamfood = true
)
// TODO(b/254512674): Tracking Bug
@@ -626,7 +625,7 @@
/** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */
@JvmField
- val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot")
+ val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 3fe6806..7ccc26c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -22,11 +22,11 @@
/**
* This class promotes best practices for flag guarding System UI view refactors.
* * [isEnabled] allows changing an implementation.
- * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
+ * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and
* ensure that it is not being invoked accidentally in the post-flag refactor.
- * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
- * flag-disabled builds, but with a check that should crash eng builds or tests when the
- * expectation is violated.
+ * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when
+ * invoked on flag-disabled builds, but with a check that should crash eng builds or tests when
+ * the expectation is violated.
*
* The constructors require that you provide a [FeatureFlags] instance. If you're using this in a
* View class, it's acceptable to ue the [forView] constructor methods, which do not require one,
@@ -60,13 +60,13 @@
* Example usage:
* ```
* public void setController(NotificationShelfController notificationShelfController) {
- * mShelfRefactor.assertDisabled();
+ * mShelfRefactor.assertInLegacyMode();
* mController = notificationShelfController;
* }
* ````
*/
- fun assertDisabled() =
- check(!isEnabled) { "Code path not supported when $flagName is enabled." }
+ fun assertInLegacyMode() =
+ check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -76,18 +76,17 @@
* Example usage:
* ```
* public void setShelfIcons(NotificationIconContainer icons) {
- * if (mShelfRefactor.expectEnabled()) {
- * mShelfIcons = icons;
- * }
+ * if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+ * mShelfIcons = icons;
* }
* ```
*/
- fun expectEnabled(): Boolean {
+ fun isUnexpectedlyInLegacyMode(): Boolean {
if (!isEnabled) {
- val message = "Code path not supported when $flagName is disabled."
+ val message = "New code path expects $flagName to be enabled."
Log.wtf(TAG, message, Exception(message))
}
- return isEnabled
+ return !isEnabled
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 20d99d1..7b33e11 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -32,6 +32,8 @@
@FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f,
/** Additional time delta to wait between drag texture vibrations */
@FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f,
+ /** Progress threshold beyond which a new drag texture is delivered */
+ @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f,
/** Number of low ticks in a drag texture composition. This is not expected to change */
val numberOfLowTicks: Int = 5,
/** Maximum velocity allowed for vibration scaling. This is not expected to change. */
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index e6de156..f313fb3 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -46,6 +46,8 @@
private val positionAccelerateInterpolator =
AccelerateInterpolator(config.progressInterpolatorFactor)
private var dragTextureLastTime = clock.elapsedRealtime()
+ var dragTextureLastProgress = -1f
+ private set
private val lowTickDurationMs =
vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0]
private var hasVibratedAtLowerBookend = false
@@ -91,6 +93,9 @@
val elapsedSinceLastDrag = currentTime - dragTextureLastTime
if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return
+ val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
+ if (deltaProgress < config.deltaProgressForDragThreshold) return
+
val velocityInterpolated =
velocityAccelerateInterpolator.getInterpolation(
min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -116,11 +121,14 @@
}
vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
dragTextureLastTime = currentTime
+ dragTextureLastProgress = normalizedSliderProgress
}
override fun onHandleAcquiredByTouch() {}
- override fun onHandleReleasedFromTouch() {}
+ override fun onHandleReleasedFromTouch() {
+ dragTextureLastProgress = -1f
+ }
override fun onLowerBookend() {
if (!hasVibratedAtLowerBookend) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 86bf368..a511713 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.view.LayoutInflater
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
import com.android.keyguard.LockIconView
@@ -71,6 +72,7 @@
private val keyguardIndicationController: KeyguardIndicationController,
private val lockIconViewController: LockIconViewController,
private val shadeInteractor: ShadeInteractor,
+ private val interactionJankMonitor: InteractionJankMonitor
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -140,6 +142,7 @@
keyguardStateController,
shadeInteractor,
{ keyguardStatusViewController!!.getClockController() },
+ interactionJankMonitor,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 081edd1..019d428 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -38,9 +38,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
-import com.android.systemui.communal.data.repository.CommunalRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule;
-import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -96,9 +93,6 @@
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
includes = {
- CommunalRepositoryModule.class,
- CommunalTutorialRepositoryModule.class,
- CommunalWidgetRepositoryModule.class,
FalsingModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index e8740a4..654f2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -90,7 +90,7 @@
* If the current user can use face auth to enter the device. This is true when the user has
* face auth enrolled, and is enabled in settings/device policy.
*/
- val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
/**
* If the current user can use face auth to enter the device right now. This is true when
@@ -348,10 +348,11 @@
.and(isFingerprintBiometricAllowed)
.stateIn(scope, SharingStarted.Eagerly, false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> =
+ override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> =
isFaceAuthenticationEnabled
.and(isFaceEnrolled)
.and(mobileConnectionsRepository.isAnySimSecure.isFalse())
+ .stateIn(scope, SharingStarted.Eagerly, false)
override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =
isFaceAuthEnrolledAndEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 38eb730..6e0aa4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -26,10 +26,11 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
-import javax.inject.Inject
@SysUISingleton
class FromAodTransitionInteractor
@@ -86,15 +87,19 @@
}
}
}
-
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
- duration = TRANSITION_DURATION_MS
+ duration =
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 500L
+ val TO_LOCKSCREEN_DURATION = 500.milliseconds
+ private val DEFAULT_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index ad51e74..c67153a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -114,7 +114,8 @@
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+ if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+ resetIfCancelled = true,
)
}
}
@@ -127,6 +128,7 @@
duration =
when (toState) {
KeyguardState.DREAMING -> TO_DREAMING_DURATION
+ KeyguardState.AOD -> TO_AOD_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -134,5 +136,6 @@
companion object {
private val DEFAULT_DURATION = 500.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
+ val TO_AOD_DURATION = 1100.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 660bd84..c39a4c9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -33,6 +33,9 @@
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -40,9 +43,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -355,6 +355,7 @@
when (toState) {
KeyguardState.DREAMING -> TO_DREAMING_DURATION
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+ KeyguardState.AOD -> TO_AOD_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -364,5 +365,6 @@
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
+ val TO_AOD_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 89aca76..85b0f4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -41,6 +41,9 @@
/** Whether face auth is in lock out state. */
fun isLockedOut(): Boolean
+ /** Whether face auth is enrolled and enabled for the current user */
+ fun isFaceAuthEnabledAndEnrolled(): Boolean
+
/**
* Register listener for use from code that cannot use [authenticationStatus] or
* [detectionStatus]
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6e19fdb..b953b48 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -39,7 +39,6 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -49,6 +48,8 @@
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -64,8 +65,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
-import javax.inject.Provider
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -148,9 +147,7 @@
.combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
isDreaming && isDozeOff(dozeTransitionModel.to)
}
- .sample(powerInteractor.isAwake) { isAbleToDream, isAwake ->
- isAbleToDream && isAwake
- }
+ .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
.flatMapLatest { isAbleToDream ->
flow {
delay(50)
@@ -217,11 +214,8 @@
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
/** Notifies when a new configuration is set */
- val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
-
- /** Represents the current state of the KeyguardRootView visibility */
- val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
- repository.keyguardRootViewVisibility
+ val configurationChange: Flow<Unit> =
+ configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
/** The position of the keyguard clock. */
val clockPosition: Flow<Position> = repository.clockPosition
@@ -235,12 +229,17 @@
R.dimen.keyguard_translate_distance_on_swipe_up
)
shadeRepository.shadeModel.map {
- // On swipe up, translate the keyguard to reveal the bouncer
- MathUtils.lerp(
- translationDistance,
- 0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
- )
+ if (it.expansionAmount == 0f) {
+ // Reset the translation value
+ 0f
+ } else {
+ // On swipe up, translate the keyguard to reveal the bouncer
+ MathUtils.lerp(
+ translationDistance,
+ 0,
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount)
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index f38bb2b..fbadde6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -43,6 +43,7 @@
override fun isLockedOut(): Boolean = false
override fun isEnabled() = false
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
override fun registerListener(listener: FaceAuthenticationListener) {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 797dec2..2641846 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
@@ -81,6 +82,7 @@
private val facePropertyRepository: FacePropertyRepository,
private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
private val powerInteractor: PowerInteractor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
) : CoreStartable, KeyguardFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -149,7 +151,10 @@
.onEach {
if (it) {
faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
- repository.setLockedOut(true)
+ // We don't care about this if face auth is not enabled.
+ if (isFaceAuthEnabledAndEnrolled()) {
+ repository.setLockedOut(true)
+ }
}
}
.launchIn(applicationScope)
@@ -263,6 +268,9 @@
}
}
+ override fun isFaceAuthEnabledAndEnrolled(): Boolean =
+ biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value
+
private fun observeFaceAuthStateUpdates() {
authenticationStatus
.onEach { authStatusUpdate ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index ac4ad39..c72e6ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -20,21 +20,24 @@
import android.view.View
import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
+import android.view.ViewGroup.OnHierarchyChangeListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -48,8 +51,6 @@
@ExperimentalCoroutinesApi
object KeyguardRootViewBinder {
- private var onLayoutChangeListener: OnLayoutChange? = null
-
@JvmStatic
fun bind(
view: ViewGroup,
@@ -60,7 +61,14 @@
keyguardStateController: KeyguardStateController,
shadeInteractor: ShadeInteractor,
clockControllerProvider: Provider<ClockController>?,
+ interactionJankMonitor: InteractionJankMonitor?,
): DisposableHandle {
+ var onLayoutChangeListener: OnLayoutChange? = null
+ val childViews = mutableMapOf<Int, View?>()
+ val statusViewId = R.id.keyguard_status_view
+ val burnInLayerId = R.id.burn_in_layer
+ val aodNotificationIconContainerId = R.id.aod_notification_icon_container
+ val largeClockId = R.id.lockscreen_clock_view_large
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -86,36 +94,74 @@
if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
- viewModel.translationY.collect { y ->
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.translationY = y
+ viewModel.burnInLayerVisibility.collect { visibility ->
+ childViews[burnInLayerId]?.visibility = visibility
+ // Reset alpha only for the icons, as they currently have their
+ // own animator
+ childViews[aodNotificationIconContainerId]?.alpha = 0f
}
}
- }
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ launch {
+ viewModel.burnInLayerAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
+ childViews[aodNotificationIconContainerId]?.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.lockscreenStateAlpha.collect { alpha ->
+ childViews[statusViewId]?.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.translationY.collect { y ->
+ childViews[burnInLayerId]?.translationY = y
+ }
+ }
+
launch {
viewModel.translationX.collect { x ->
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.translationX = x
+ childViews[burnInLayerId]?.translationX = x
}
}
- }
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
launch {
viewModel.scale.collect { (scale, scaleClockOnly) ->
if (scaleClockOnly) {
- val largeClock =
- view.findViewById<View?>(R.id.lockscreen_clock_view_large)
- largeClock?.let {
+ childViews[largeClockId]?.let {
it.scaleX = scale
it.scaleY = scale
}
} else {
- val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
- burnInLayer.scaleX = scale
- burnInLayer.scaleY = scale
+ childViews[burnInLayerId]?.scaleX = scale
+ childViews[burnInLayerId]?.scaleY = scale
+ }
+ }
+ }
+
+ interactionJankMonitor?.let { jankMonitor ->
+ launch {
+ viewModel.goneToAodTransition.collect {
+ when (it.transitionState) {
+ TransitionState.STARTED -> {
+ val clockId =
+ clockControllerProvider?.get()?.config?.id
+ ?: MISSING_CLOCK_ID
+ val builder =
+ InteractionJankMonitor.Configuration.Builder
+ .withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
+ .setTag(clockId)
+
+ jankMonitor.begin(builder)
+ }
+ TransitionState.CANCELED ->
+ jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.FINISHED ->
+ jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+ TransitionState.RUNNING -> Unit
+ }
}
}
}
@@ -132,54 +178,32 @@
}
}
}
-
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- launch {
- viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
- view.animate().cancel()
- val goingToFullShade = visibilityState.goingToFullShade
- val statusBarState = visibilityState.statusBarState
- val isOcclusionTransitionRunning =
- visibilityState.occlusionTransitionRunning
- if (goingToFullShade) {
- view
- .animate()
- .alpha(0f)
- .setStartDelay(
- keyguardStateController.keyguardFadingAwayDelay
- )
- .setDuration(
- keyguardStateController.shortenedFadingAwayDuration
- )
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction { view.visibility = View.GONE }
- .start()
- } else if (
- statusBarState == StatusBarState.KEYGUARD ||
- statusBarState == StatusBarState.SHADE_LOCKED
- ) {
- view.visibility = View.VISIBLE
- if (!isOcclusionTransitionRunning) {
- view.alpha = 1f
- }
- } else {
- view.visibility = View.GONE
- }
- }
- }
- }
- }
}
viewModel.clockControllerProvider = clockControllerProvider
onLayoutChangeListener = OnLayoutChange(viewModel)
view.addOnLayoutChangeListener(onLayoutChangeListener)
+ // Views will be added or removed after the call to bind(). This is needed to avoid many
+ // calls to findViewById
+ view.setOnHierarchyChangeListener(
+ object : OnHierarchyChangeListener {
+ override fun onChildViewAdded(parent: View, child: View) {
+ childViews.put(child.id, view)
+ }
+
+ override fun onChildViewRemoved(parent: View, child: View) {
+ childViews.remove(child.id)
+ }
+ }
+ )
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
view.removeOnLayoutChangeListener(onLayoutChangeListener)
+ view.setOnHierarchyChangeListener(null)
+ childViews.clear()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
index f3586ba..2feaa2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt
@@ -24,6 +24,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/**
@@ -46,8 +47,8 @@
constraintLayout: ConstraintLayout,
viewModel: KeyguardBlueprintViewModel,
finishedAddViewCallback: () -> Unit
- ) {
- constraintLayout.repeatWhenAttached {
+ ): DisposableHandle {
+ return constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4a2954d..5a4bbef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -27,6 +27,7 @@
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
+import android.view.ContextThemeWrapper
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DisplayInfo
@@ -179,7 +180,11 @@
fun render() {
mainHandler.post {
- val previewContext = display?.let { context.createDisplayContext(it) } ?: context
+ val previewContext =
+ display?.let {
+ ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
+ }
+ ?: context
val rootView = FrameLayout(previewContext)
@@ -322,7 +327,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
- val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() }
+ val keyguardRootView = KeyguardRootView(previewContext, null)
disposables.add(
KeyguardRootViewBinder.bind(
keyguardRootView,
@@ -333,6 +338,7 @@
keyguardStateController,
shadeInteractor,
null, // clock provider only needed for burn in
+ null, // jank monitor not required for preview mode
)
)
rootView.addView(
@@ -342,26 +348,29 @@
FrameLayout.LayoutParams.MATCH_PARENT,
),
)
- PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
- if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setupShortcuts(keyguardRootView)
- }
- setUpUdfps(previewContext, rootView)
- if (!shouldHideClock) {
- setUpClock(previewContext, rootView)
- KeyguardPreviewClockViewBinder.bind(
- largeClockHostView,
- smallClockHostView,
- clockViewModel,
- )
- }
+ disposables.add(
+ PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ setupShortcuts(keyguardRootView)
+ }
+ setUpUdfps(previewContext, rootView)
- setUpSmartspace(previewContext, rootView)
- smartSpaceView?.let {
- KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ if (!shouldHideClock) {
+ setUpClock(previewContext, rootView)
+ KeyguardPreviewClockViewBinder.bind(
+ largeClockHostView,
+ smallClockHostView,
+ clockViewModel,
+ )
+ }
+
+ setUpSmartspace(previewContext, rootView)
+ smartSpaceView?.let {
+ KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
+ }
}
- }
+ )
}
private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..024707a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class AodToLockscreenTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_LOCKSCREEN_DURATION,
+ transitionFlow = interactor.aodToLockscreenTransition,
+ )
+
+ /** Ensure alpha is set to be visible */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 500.milliseconds,
+ onStart = { 1f },
+ onStep = { 1f },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
new file mode 100644
index 0000000..601dbcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */
+@SysUISingleton
+class GoneToAodTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_AOD_DURATION,
+ transitionFlow = interactor.goneToAodTransition,
+ )
+
+ /** y-translation from the top of the screen for AOD */
+ fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
+ return transitionAnimation.createFlow(
+ startTime = 600.milliseconds,
+ duration = 500.milliseconds,
+ onStart = { translatePx },
+ onStep = { translatePx + it * -translatePx },
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED_DECELERATE,
+ )
+ }
+
+ /** alpha animation upon entering AOD */
+ val enterFromTopAnimationAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ startTime = 600.milliseconds,
+ duration = 500.milliseconds,
+ onStart = { 0f },
+ onStep = { it },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 89835fe..1f98082 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,14 +17,19 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.Context
import android.util.MathUtils
+import android.view.View.VISIBLE
import com.android.app.animation.Interpolators
import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,17 +37,23 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardRootViewModel
@Inject
constructor(
+ private val context: Context,
private val keyguardInteractor: KeyguardInteractor,
private val burnInInteractor: BurnInInteractor,
+ private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -56,9 +67,12 @@
public var clockControllerProvider: Provider<ClockController>? = null
- /** Represents the current state of the KeyguardRootView visibility */
- val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
- keyguardInteractor.keyguardRootViewVisibilityState
+ val burnInLayerVisibility: Flow<Int> =
+ keyguardTransitionInteractor.startedKeyguardState
+ .filter { it == AOD || it == LOCKSCREEN }
+ .map { VISIBLE }
+
+ val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
@@ -70,9 +84,14 @@
}
}
- private val burnIn: Flow<BurnInModel> =
- combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn
- ->
+ private fun burnIn(): Flow<BurnInModel> {
+ val dozingAmount: Flow<Float> =
+ merge(
+ keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+ )
+
+ return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
val useScaleOnly =
clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false
@@ -91,13 +110,61 @@
)
}
}
+ }
+
+ /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
+ val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
+
+ /** For elements that appear and move during the animation -> AOD */
+ val burnInLayerAlpha: Flow<Float> =
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(1f)
+ } else {
+ goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+ }
+ }
val translationY: Flow<Float> =
- merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() })
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(0f)
+ } else {
+ keyguardInteractor.configurationChange.flatMapLatest { _ ->
+ val enterFromTopAmount =
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_enter_from_top_translation_y
+ )
+ combine(
+ keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+ burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
+ goneToAodTransitionViewModel
+ .enterFromTopTranslationY(enterFromTopAmount)
+ .onStart { emit(0f) },
+ ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY ->
+ // All 3 values need to be combined for a smooth translation
+ keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY
+ }
+ }
+ }
+ }
- val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() }
+ val translationX: Flow<Float> =
+ previewMode.flatMapLatest {
+ if (it.isInPreviewMode) {
+ flowOf(0f)
+ } else {
+ burnIn().map { it.translationX.toFloat() }
+ }
+ }
- val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) }
+ val scale: Flow<Pair<Float, Boolean>> =
+ previewMode.flatMapLatest { previewMode ->
+ burnIn().map {
+ val scale = if (previewMode.isInPreviewMode) 1f else it.scale
+ Pair(scale, it.scaleClockOnly)
+ }
+ }
/**
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
@@ -105,11 +172,14 @@
* lock screen.
*/
fun enablePreviewMode() {
- val newPreviewMode = PreviewMode(true)
- previewMode.value = newPreviewMode
+ previewMode.value = PreviewMode(true)
}
fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) {
+ // Notifications should not be visible in preview mode
+ if (previewMode.value.isInPreviewMode) {
+ return
+ }
keyguardInteractor.sharedNotificationContainerPosition.value =
SharedNotificationContainerPosition(top, bottom)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index 8634b09..a53f0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -19,7 +19,11 @@
import android.os.Process
import android.os.RemoteException
import android.util.Log
-import com.android.internal.util.FrameworkStatsLog
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -36,21 +40,23 @@
*
* @param sessionCreationSource The entry point requesting permission to capture.
*/
- fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) {
- // TODO check that state & SessionCreationSource matches expected values
- notifyToServer(state, sessionCreationSource)
+ fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) {
+ notifyToServer(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+ sessionCreationSource
+ )
}
/**
* Request to log that the permission request moved to the given state.
*
- * Should not be used for the initialization state, since that
+ * Should not be used for the initialization state, since that should use {@link
+ * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the
+ * sessionCreationSource.
*/
fun notifyPermissionProgress(state: Int) {
// TODO validate state is valid
- notifyToServer(
- state,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN)
+ notifyToServer(state, SessionCreationSource.UNKNOWN)
}
/**
@@ -64,16 +70,21 @@
* Indicates the entry point for requesting the permission. Must be a valid state defined in
* the SessionCreationSource enum.
*/
- private fun notifyToServer(state: Int, sessionCreationSource: Int) {
+ private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) {
Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
try {
service.notifyPermissionRequestStateChange(
- Process.myUid(), state, sessionCreationSource)
+ Process.myUid(),
+ state,
+ sessionCreationSource.toMetricsConstant()
+ )
} catch (e: RemoteException) {
Log.e(
TAG,
- "Error notifying server of permission flow state $state from source $sessionCreationSource",
- e)
+ "Error notifying server of permission flow state $state from source " +
+ "$sessionCreationSource",
+ e
+ )
}
}
@@ -81,3 +92,18 @@
const val TAG = "MediaProjectionMetricsLogger"
}
}
+
+enum class SessionCreationSource {
+ APP,
+ CAST,
+ SYSTEM_UI_SCREEN_RECORDER,
+ UNKNOWN;
+
+ fun toMetricsConstant(): Int =
+ when (this) {
+ APP -> METRICS_CREATION_SOURCE_APP
+ CAST -> METRICS_CREATION_SOURCE_CAST
+ SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+ UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index b5d3e91..0bbcfd9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -87,14 +87,15 @@
override fun getLayoutResource() = R.layout.media_projection_app_selector
- public override fun onCreate(bundle: Bundle?) {
+ public override fun onCreate(savedInstanceState: Bundle?) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
component =
componentFactory.create(
hostUserHandle = hostUserHandle,
callingPackage = callingPackage,
view = this,
- resultHandler = this
+ resultHandler = this,
+ isFirstStart = savedInstanceState == null
)
component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
@@ -113,7 +114,7 @@
reviewGrantedConsentRequired =
intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
- super.onCreate(bundle)
+ super.onCreate(savedInstanceState)
controller.init()
// we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
// our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 2217509..8c6f307 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -146,6 +146,9 @@
@BindsInstance @MediaProjectionAppSelector callingPackage: String?,
@BindsInstance view: MediaProjectionAppSelectorView,
@BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+ // Whether the app selector is starting for the first time. False when it is re-starting
+ // due to a config change.
+ @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,
): MediaProjectionAppSelectorComponent
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index fced117..69132d3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -18,6 +18,8 @@
import android.content.ComponentName
import android.os.UserHandle
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -43,9 +45,17 @@
@MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
@MediaProjectionAppSelector private val callerPackageName: String?,
private val thumbnailLoader: RecentTaskThumbnailLoader,
+ @MediaProjectionAppSelector private val isFirstStart: Boolean,
+ private val logger: MediaProjectionMetricsLogger,
) {
fun init() {
+ // Only log during the first start of the app selector.
+ // Don't log when the app selector restarts due to a config change.
+ if (isFirstStart) {
+ logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
scope.launch {
val recentTasks = recentTaskListProvider.loadRecentTasks()
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index a9e6c53..e9b4582 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -22,6 +22,7 @@
data class RecentTask(
val taskId: Int,
+ val displayId: Int,
@UserIdInt val userId: Int,
val topActivityComponent: ComponentName?,
val baseIntentComponent: ComponentName?,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index aa4c4e5..730aa62 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -60,6 +60,7 @@
.map {
RecentTask(
it.taskId,
+ it.displayId,
it.userId,
it.topActivity,
it.baseIntent?.component,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index fd1a683..ba837db 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -130,10 +130,10 @@
view.width,
view.height
)
- activityOptions.setPendingIntentBackgroundActivityStartMode(
+ activityOptions.pendingIntentBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
activityOptions.launchCookie = launchCookie
+ activityOptions.launchDisplayId = task.displayId
activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
resultHandler.returnSelectedApp(launchCookie)
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 d08d040..fa418fc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,7 +53,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
@@ -74,6 +76,7 @@
private final FeatureFlags mFeatureFlags;
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
private final StatusBarManager mStatusBarManager;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private String mPackageName;
private int mUid;
@@ -90,15 +93,17 @@
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
- StatusBarManager statusBarManager) {
+ StatusBarManager statusBarManager,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
}
@Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
final Intent launchingIntent = getIntent();
mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
@@ -133,6 +138,10 @@
try {
if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ SessionCreationSource.APP);
+ }
final IMediaProjection projection =
MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
mReviewGrantedConsentRequired);
@@ -231,6 +240,13 @@
mDialog = dialogBuilder.create();
}
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ appName == null
+ ? SessionCreationSource.CAST
+ : SessionCreationSource.APP);
+ }
+
setUpDialog(mDialog);
mDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index c4749e0..c77f3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -231,7 +231,6 @@
animation.removeEndListener(this)
if (!canceled) {
-
// The delay between finishing this animation and starting the runnable
val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
@@ -461,7 +460,6 @@
}
private fun handleMoveEvent(event: MotionEvent) {
-
val x = event.x
val y = event.y
@@ -927,17 +925,7 @@
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
updateRestingArrowDimens()
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
- )
- } else {
- vibratorHelper.cancel()
- mainHandler.postDelayed(10L) {
- vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
- }
- }
+ performActivatedHapticFeedback()
val popVelocity =
if (previousState == GestureState.INACTIVE) {
POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
@@ -958,25 +946,24 @@
mView.popOffEdge(POP_ON_INACTIVE_VELOCITY)
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
- )
- } else {
- vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
- }
+ performDeactivatedHapticFeedback()
updateRestingArrowDimens()
}
GestureState.FLUNG -> {
+ // Typically a vibration is only played while transitioning to ACTIVE. However there
+ // are instances where a fling to trigger back occurs while not in that state.
+ // (e.g. A fling is detected before crossing the trigger threshold.)
+ if (previousState != GestureState.ACTIVE) {
+ performActivatedHapticFeedback()
+ }
mainHandler.postDelayed(POP_ON_FLING_DELAY) {
mView.popScale(POP_ON_FLING_VELOCITY)
}
- updateRestingArrowDimens()
mainHandler.postDelayed(
onEndSetCommittedStateListener.runnable,
MIN_DURATION_FLING_ANIMATION
)
+ updateRestingArrowDimens()
}
GestureState.COMMITTED -> {
// In most cases, animating between states is handled via `updateRestingArrowDimens`
@@ -1011,6 +998,31 @@
}
}
+ private fun performDeactivatedHapticFeedback() {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ vibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+ )
+ } else {
+ vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+ }
+ }
+
+ private fun performActivatedHapticFeedback() {
+ if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ vibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+ )
+ } else {
+ vibratorHelper.cancel()
+ mainHandler.postDelayed(10L) {
+ vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+ }
+ }
+ }
+
private fun convertVelocityToAnimationFactor(
valueOnFastVelocity: Float,
valueOnSlowVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 733383e..58c4f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -96,43 +96,9 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-/** Functions that help creating the People tile layouts. */
-public class PeopleTileViewHelper {
- /** Turns on debugging information about People Space. */
- private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
- private static final String TAG = "PeopleTileView";
-
- private static final int DAYS_IN_A_WEEK = 7;
- private static final int ONE_DAY = 1;
-
- public static final int LAYOUT_SMALL = 0;
- public static final int LAYOUT_MEDIUM = 1;
- public static final int LAYOUT_LARGE = 2;
-
- private static final int MIN_CONTENT_MAX_LINES = 2;
- private static final int NAME_MAX_LINES_WITHOUT_LAST_INTERACTION = 3;
- private static final int NAME_MAX_LINES_WITH_LAST_INTERACTION = 1;
-
- private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_NOTIF_CONTENT = 16 + 22 + 8 + 16;
- private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_STATUS_CONTENT = 16 + 16 + 24 + 4 + 16;
- private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
- private static final int MAX_MEDIUM_PADDING = 16;
- private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
- private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
- private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
- private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
- private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
-
- private static final int MESSAGES_COUNT_OVERFLOW = 6;
-
- private static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
-
- private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
- private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
- private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
- private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
-
- static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
+/** Variables and functions that is related to Emoji. */
+class EmojiHelper {
+ static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
// This regex can be used to match Unicode emoji characters and character sequences. It's from
// the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor
@@ -165,7 +131,44 @@
+ "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})"
+ "?)*";
- private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+ static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX);
+}
+
+/** Functions that help creating the People tile layouts. */
+public class PeopleTileViewHelper {
+ /** Turns on debugging information about People Space. */
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+ private static final String TAG = "PeopleTileView";
+
+ private static final int DAYS_IN_A_WEEK = 7;
+ private static final int ONE_DAY = 1;
+
+ public static final int LAYOUT_SMALL = 0;
+ public static final int LAYOUT_MEDIUM = 1;
+ public static final int LAYOUT_LARGE = 2;
+
+ private static final int MIN_CONTENT_MAX_LINES = 2;
+ private static final int NAME_MAX_LINES_WITHOUT_LAST_INTERACTION = 3;
+ private static final int NAME_MAX_LINES_WITH_LAST_INTERACTION = 1;
+
+ private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_NOTIF_CONTENT = 16 + 22 + 8 + 16;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_STATUS_CONTENT = 16 + 16 + 24 + 4 + 16;
+ private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
+ private static final int MAX_MEDIUM_PADDING = 16;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
+
+ private static final int MESSAGES_COUNT_OVERFLOW = 6;
+
+ private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
+ private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
+ private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
+ private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+
+ static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
public static final String EMPTY_STRING = "";
@@ -831,7 +834,7 @@
if (status.getActivity() == ACTIVITY_BIRTHDAY
|| status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) {
- setEmojiBackground(views, EMOJI_CAKE);
+ setEmojiBackground(views, EmojiHelper.EMOJI_CAKE);
}
Icon statusIcon = status.getIcon();
@@ -1072,7 +1075,7 @@
/** Returns emoji if {@code message} has two of the same emoji in sequence. */
@VisibleForTesting
CharSequence getDoubleEmoji(CharSequence message) {
- Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message);
+ Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message);
// Stores the start and end indices of each matched emoji.
List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>();
// Stores each emoji text.
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 7a0c087..f469c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -38,6 +38,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
@@ -45,13 +47,13 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
+import dagger.Lazy;
+
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* Helper class to initiate a screen recording
*/
@@ -71,6 +73,7 @@
private final FeatureFlags mFlags;
private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
@@ -115,7 +118,8 @@
FeatureFlags flags,
UserContextProvider userContextProvider,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
mMainExecutor = mainExecutor;
mContext = context;
mFlags = flags;
@@ -123,6 +127,7 @@
mBroadcastDispatcher = broadcastDispatcher;
mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -149,6 +154,9 @@
return new ScreenCaptureDisabledDialog(mContext);
}
+ mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
activityStarter, mUserContextProvider, onStartRecordingClicked)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 5154067..c34fd42 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,11 +20,10 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
@@ -36,16 +35,15 @@
suspend fun process(screenshot: ScreenshotData): ScreenshotData
}
-/**
- * Implementation of [ScreenshotRequestProcessor]
- */
+/** Implementation of [ScreenshotRequestProcessor] */
@SysUISingleton
-class RequestProcessor @Inject constructor(
- private val capture: ImageCapture,
- private val policy: ScreenshotPolicy,
- private val flags: FeatureFlags,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
+class RequestProcessor
+@Inject
+constructor(
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
@@ -67,8 +65,9 @@
result.userHandle = info.user
if (policy.isManagedProfile(info.user.identifier)) {
- val image = capture.captureTask(info.taskId)
- ?: error("Task snapshot returned a null Bitmap!")
+ val image =
+ capture.captureTask(info.taskId)
+ ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
// Provide the task snapshot as the screenshot
result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -97,3 +96,6 @@
}
private const val TAG = "RequestProcessor"
+
+/** Exception thrown by [RequestProcessor] if something goes wrong. */
+class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 127a57e..21a08a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -325,7 +325,7 @@
Context context,
FeatureFlags flags,
ScreenshotSmartActions screenshotSmartActions,
- ScreenshotNotificationsController screenshotNotificationsController,
+ ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
@@ -346,7 +346,7 @@
@Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
- mNotificationsController = screenshotNotificationsController;
+ mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
deleted file mode 100644
index 4344fd1..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.Context.NOTIFICATION_SERVICE;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.res.R;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.util.NotificationChannels;
-
-import javax.inject.Inject;
-
-/**
- * Convenience class to handle showing and hiding notifications while taking a screenshot.
- */
-public class ScreenshotNotificationsController {
- private static final String TAG = "ScreenshotNotificationManager";
-
- private final Context mContext;
- private final Resources mResources;
- private final NotificationManager mNotificationManager;
-
- @Inject
- ScreenshotNotificationsController(Context context, WindowManager windowManager) {
- mContext = context;
- mResources = context.getResources();
- mNotificationManager =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
- }
-
- /**
- * Sends a notification that the screenshot capture has failed.
- */
- public void notifyScreenshotError(int msgResId) {
- Resources res = mContext.getResources();
- String errorMsg = res.getString(msgResId);
-
- // Repurpose the existing notification to notify the user of the error
- Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
- .setTicker(res.getString(R.string.screenshot_failed_title))
- .setContentTitle(res.getString(R.string.screenshot_failed_title))
- .setContentText(errorMsg)
- .setSmallIcon(R.drawable.stat_notify_image_error)
- .setWhen(System.currentTimeMillis())
- .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
- .setCategory(Notification.CATEGORY_ERROR)
- .setAutoCancel(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- final DevicePolicyManager dpm =
- (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- final Intent intent =
- dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
- if (intent != null) {
- final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
- b.setContentIntent(pendingIntent);
- }
-
- SystemUIApplication.overrideNotificationAppName(mContext, b, true);
-
- Notification n = new Notification.BigTextStyle(b)
- .bigText(errorMsg)
- .build();
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
new file mode 100644
index 0000000..d874eb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.screenshot
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserHandle
+import android.view.Display
+import com.android.internal.R
+import com.android.internal.messages.nano.SystemMessageProto
+import com.android.systemui.SystemUIApplication
+import com.android.systemui.util.NotificationChannels
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Convenience class to handle showing and hiding notifications while taking a screenshot. */
+class ScreenshotNotificationsController
+@AssistedInject
+internal constructor(
+ @Assisted private val displayId: Int,
+ private val context: Context,
+ private val notificationManager: NotificationManager,
+ private val devicePolicyManager: DevicePolicyManager,
+) {
+ private val res = context.resources
+
+ /**
+ * Sends a notification that the screenshot capture has failed.
+ *
+ * Errors for the non-default display are shown in a unique separate notification.
+ */
+ fun notifyScreenshotError(msgResId: Int) {
+ val displayErrorString =
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ " ($externalDisplayString)"
+ } else {
+ ""
+ }
+ val errorMsg = res.getString(msgResId) + displayErrorString
+
+ // Repurpose the existing notification or create a new one
+ val builder =
+ Notification.Builder(context, NotificationChannels.ALERTS)
+ .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title))
+ .setContentTitle(
+ res.getString(com.android.systemui.res.R.string.screenshot_failed_title)
+ )
+ .setContentText(errorMsg)
+ .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error)
+ .setWhen(System.currentTimeMillis())
+ .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+ .setCategory(Notification.CATEGORY_ERROR)
+ .setAutoCancel(true)
+ .setColor(context.getColor(R.color.system_notification_accent_color))
+ val intent =
+ devicePolicyManager.createAdminSupportIntent(
+ DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE
+ )
+ if (intent != null) {
+ val pendingIntent =
+ PendingIntent.getActivityAsUser(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE,
+ null,
+ UserHandle.CURRENT
+ )
+ builder.setContentIntent(pendingIntent)
+ }
+ SystemUIApplication.overrideNotificationAppName(context, builder, true)
+ val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build()
+ // A different id for external displays to keep the 2 error notifications separated.
+ val id =
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT
+ } else {
+ SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY
+ }
+ notificationManager.notify(id, notification)
+ }
+
+ private val externalDisplayString: String
+ get() =
+ res.getString(
+ com.android.systemui.res.R.string.screenshot_failed_external_display_indication
+ )
+
+ /** Factory for [ScreenshotNotificationsController]. */
+ @AssistedFactory
+ interface Factory {
+ fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index 070fb1e..049799e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,25 +16,29 @@
package com.android.systemui.screenshot;
+import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.view.WindowManager;
+import android.view.Display;
import com.android.systemui.res.R;
/**
- * Performs a number of miscellaneous, non-system-critical actions
- * after the system has finished booting.
+ * Receives errors related to screenshot.
*/
public class ScreenshotServiceErrorReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
// Show a message that we've failed to save the image to disk
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- ScreenshotNotificationsController controller =
- new ScreenshotNotificationsController(context, wm);
+ NotificationManager notificationManager = context.getSystemService(
+ NotificationManager.class);
+ DevicePolicyManager devicePolicyManager = context.getSystemService(
+ DevicePolicyManager.class);
+ ScreenshotNotificationsController controller = new ScreenshotNotificationsController(
+ Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager);
controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index abe40ff..0158284 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -10,6 +10,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import java.util.function.Consumer
import javax.inject.Inject
@@ -34,7 +36,8 @@
displayRepository: DisplayRepository,
@Application private val mainScope: CoroutineScope,
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
- private val uiEventLogger: UiEventLogger
+ private val uiEventLogger: UiEventLogger,
+ private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
) {
private lateinit var displays: StateFlow<Set<Display>>
@@ -44,6 +47,7 @@
}
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
+ private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
/**
* Executes the [ScreenshotRequest].
@@ -58,40 +62,68 @@
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
- screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
+ displayIds.forEach { displayId: Int ->
dispatchToController(
- screenshotData = screenshotData,
+ rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
onSaved =
- if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> },
- callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId)
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ onSaved
+ } else { _ -> },
+ callback = resultCallbackWrapper.createCallbackForId(displayId)
)
}
}
- /** Creates a [ScreenshotData] for each display. */
- private suspend fun ScreenshotRequest.oneForEachDisplay(
- displayIds: List<Int>
- ): List<ScreenshotData> {
- return displayIds
- .map { displayId -> ScreenshotData.fromRequest(this, displayId) }
- .map { screenshotData: ScreenshotData ->
- screenshotRequestProcessor.process(screenshotData)
- }
- }
-
- private fun dispatchToController(
- screenshotData: ScreenshotData,
+ /** All logging should be triggered only by this method. */
+ private suspend fun dispatchToController(
+ rawScreenshotData: ScreenshotData,
onSaved: (Uri) -> Unit,
callback: RequestCallback
) {
+ // Let's wait before logging "screenshot requested", as we should log the processed
+ // ScreenshotData.
+ val screenshotData =
+ try {
+ screenshotRequestProcessor.process(rawScreenshotData)
+ } catch (e: RequestProcessorException) {
+ Log.e(TAG, "Failed to process screenshot request!", e)
+ logScreenshotRequested(rawScreenshotData)
+ onFailedScreenshotRequest(rawScreenshotData, callback)
+ return
+ }
+
+ logScreenshotRequested(screenshotData)
+ Log.d(TAG, "Screenshot request: $screenshotData")
+ try {
+ getScreenshotController(screenshotData.displayId)
+ .handleScreenshot(screenshotData, onSaved, callback)
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e)
+ onFailedScreenshotRequest(screenshotData, callback)
+ return // After a failure log, nothing else should run.
+ }
+ }
+
+ /**
+ * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED]
+ * event.
+ */
+ private fun logScreenshotRequested(screenshotData: ScreenshotData) {
uiEventLogger.log(
ScreenshotEvent.getScreenshotSource(screenshotData.source),
0,
screenshotData.packageNameString
)
- Log.d(TAG, "Screenshot request: $screenshotData")
- getScreenshotController(screenshotData.displayId)
- .handleScreenshot(screenshotData, onSaved, callback)
+ }
+
+ private fun onFailedScreenshotRequest(
+ screenshotData: ScreenshotData,
+ callback: RequestCallback
+ ) {
+ uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString)
+ getNotificationController(screenshotData.displayId)
+ .notifyScreenshotError(R.string.screenshot_failed_to_capture_text)
+ callback.reportError()
}
private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
@@ -140,6 +172,12 @@
}
}
+ private fun getNotificationController(id: Int): ScreenshotNotificationsController {
+ return notificationControllers.computeIfAbsent(id) {
+ screenshotNotificationControllerFactory.create(id)
+ }
+ }
+
/** For java compatibility only. see [executeScreenshots] */
fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 75d52cb..0991c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -113,7 +113,8 @@
@Inject
public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,
UserManager userManager, DevicePolicyManager devicePolicyManager,
- UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController,
+ UiEventLogger uiEventLogger,
+ ScreenshotNotificationsController.Factory notificationsControllerFactory,
Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,
RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {
if (DEBUG_SERVICE) {
@@ -123,7 +124,7 @@
mUserManager = userManager;
mDevicePolicyManager = devicePolicyManager;
mUiEventLogger = uiEventLogger;
- mNotificationsController = notificationsController;
+ mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);
mContext = context;
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
@@ -246,15 +247,17 @@
Log.d(TAG, "Processing screenshot data");
- ScreenshotData screenshotData = ScreenshotData.fromRequest(
- request, Display.DEFAULT_DISPLAY);
+ if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
+ mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
+ return;
+ }
+ // TODO(b/295143676): Delete the following after the flag is released.
try {
- if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) {
- mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback);
- } else {
- mProcessor.processAsync(screenshotData, (data) ->
- dispatchToController(data, onSaved, callback));
- }
+ ScreenshotData screenshotData = ScreenshotData.fromRequest(
+ request, Display.DEFAULT_DISPLAY);
+ mProcessor.processAsync(screenshotData, (data) ->
+ dispatchToController(data, onSaved, callback));
+
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to process screenshot request!", e);
logFailedRequest(request);
@@ -264,6 +267,7 @@
}
}
+ // TODO(b/295143676): Delete this.
private void dispatchToController(ScreenshotData screenshot,
Consumer<Uri> uriConsumer, RequestCallback callback) {
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6783afa..1ecb127 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,7 @@
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
import android.app.Activity;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.Gravity;
@@ -35,8 +36,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -74,21 +75,26 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setWindowAttributes();
+ setContentView(R.layout.brightness_mirror_container);
+ setBrightnessDialogViewAttributes();
+ }
+ private void setWindowAttributes() {
final Window window = getWindow();
- window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+ window.setGravity(Gravity.TOP | Gravity.LEFT);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
// Calling this creates the decor View, so setLayout takes proper effect
// (see Dialog#onWindowAttributesChanged)
window.getDecorView();
- window.setLayout(
- WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+ window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
+ }
- setContentView(R.layout.brightness_mirror_container);
+ void setBrightnessDialogViewAttributes() {
FrameLayout frame = findViewById(R.id.brightness_mirror_container);
// The brightness mirror container is INVISIBLE by default.
frame.setVisibility(View.VISIBLE);
@@ -97,6 +103,14 @@
getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
lp.leftMargin = horizontalMargin;
lp.rightMargin = horizontalMargin;
+
+ int verticalMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.notification_guts_option_vertical_padding);
+
+ lp.topMargin = verticalMargin;
+ lp.bottomMargin = verticalMargin;
+
frame.setLayoutParams(lp);
Rect bounds = new Rect();
frame.addOnLayoutChangeListener(
@@ -113,6 +127,20 @@
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
mBrightnessController = mBrightnessControllerFactory.create(controller);
+
+ Configuration configuration = getResources().getConfiguration();
+ int orientation = configuration.orientation;
+
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
+ - lp.leftMargin * 2;
+ } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ lp.width = getWindowManager().getDefaultDisplay().getWidth()
+ - lp.leftMargin * 2;
+ }
+
+ frame.setLayoutParams(lp);
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 51972c2..8dc97c0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1446,9 +1446,7 @@
mBarState);
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setKeyguardVisibility(mBarState, false);
- } else {
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setKeyguardBottomAreaVisibility(mBarState, false);
}
@@ -2358,14 +2356,6 @@
}
}
- private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
- mKeyguardInteractor.setKeyguardRootVisibility(
- statusBarState,
- goingToFullShade,
- mIsOcclusionTransitionRunning
- );
- }
-
@Deprecated
private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
mKeyguardBottomArea.animate().cancel();
@@ -4443,11 +4433,13 @@
&& statusBarState == KEYGUARD) {
// This means we're doing the screen off animation - position the keyguard status
// view where it'll be on AOD, so we can animate it in.
- mKeyguardStatusViewController.updatePosition(
- mClockPositionResult.clockX,
- mClockPositionResult.clockYFullyDozing,
- mClockPositionResult.clockScale,
- false /* animate */);
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ mKeyguardStatusViewController.updatePosition(
+ mClockPositionResult.clockX,
+ mClockPositionResult.clockYFullyDozing,
+ mClockPositionResult.clockScale,
+ false /* animate */);
+ }
}
mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
@@ -4456,9 +4448,7 @@
goingToFullShade,
mBarState);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- setKeyguardVisibility(statusBarState, goingToFullShade);
- } else {
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
}
@@ -4562,7 +4552,12 @@
public void showAodUi() {
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
- mStatusBarStateListener.onStateChanged(KEYGUARD);
+
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ mStatusBarStateController.setState(KEYGUARD);
+ } else {
+ mStatusBarStateListener.onStateChanged(KEYGUARD);
+ }
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
setExpandedFraction(1f);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
deleted file mode 100644
index 17b4e3b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Point
-import android.graphics.Rect
-import android.renderscript.Allocation
-import android.renderscript.Element
-import android.renderscript.RenderScript
-import android.renderscript.ScriptIntrinsicBlur
-import android.util.Log
-import android.util.MathUtils
-import com.android.internal.graphics.ColorUtils
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-import javax.inject.Inject
-
-private const val TAG = "MediaArtworkProcessor"
-private const val COLOR_ALPHA = (255 * 0.7f).toInt()
-private const val BLUR_RADIUS = 25f
-private const val DOWNSAMPLE = 6
-
-@SysUISingleton
-class MediaArtworkProcessor @Inject constructor() {
-
- private val mTmpSize = Point()
- private var mArtworkCache: Bitmap? = null
-
- fun processArtwork(context: Context, artwork: Bitmap): Bitmap? {
- if (mArtworkCache != null) {
- return mArtworkCache
- }
- val renderScript = RenderScript.create(context)
- val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
- var input: Allocation? = null
- var output: Allocation? = null
- var inBitmap: Bitmap? = null
- try {
- @Suppress("DEPRECATION")
- context.display?.getSize(mTmpSize)
- val rect = Rect(0, 0, artwork.width, artwork.height)
- MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
- inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
- true /* filter */)
- // Render script blurs only support ARGB_8888, we need a conversion if we got a
- // different bitmap config.
- if (inBitmap.config != Bitmap.Config.ARGB_8888) {
- val oldIn = inBitmap
- inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
- oldIn.recycle()
- }
- val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
- Bitmap.Config.ARGB_8888)
-
- input = Allocation.createFromBitmap(renderScript, inBitmap,
- Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
- output = Allocation.createFromBitmap(renderScript, outBitmap)
-
- blur.setRadius(BLUR_RADIUS)
- blur.setInput(input)
- blur.forEach(output)
- output.copyTo(outBitmap)
-
- val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
-
- val canvas = Canvas(outBitmap)
- canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA))
- return outBitmap
- } catch (ex: IllegalArgumentException) {
- Log.e(TAG, "error while processing artwork", ex)
- return null
- } finally {
- input?.destroy()
- output?.destroy()
- blur.destroy()
- inBitmap?.recycle()
- }
- }
-
- fun clearCache() {
- mArtworkCache?.recycle()
- mArtworkCache = null
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5bd40b8..389486f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,43 +15,29 @@
*/
package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
-
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
-import android.os.AsyncTask;
import android.os.Trace;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
-import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.models.player.MediaData;
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
@@ -65,18 +51,11 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ScrimState;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import dagger.Lazy;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -85,7 +64,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -99,7 +77,6 @@
private final StatusBarStateController mStatusBarStateController;
private final SysuiColorExtractor mColorExtractor;
private final KeyguardStateController mKeyguardStateController;
- private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
static {
@@ -117,9 +94,6 @@
private final NotifCollection mNotifCollection;
@Nullable
- private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
-
- @Nullable
private BiometricUnlockController mBiometricUnlockController;
@Nullable
private ScrimController mScrimController;
@@ -128,12 +102,8 @@
@VisibleForTesting
boolean mIsLockscreenLiveWallpaperEnabled;
- private final DelayableExecutor mMainExecutor;
-
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
- private final MediaArtworkProcessor mMediaArtworkProcessor;
- private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
@@ -150,8 +120,6 @@
private List<String> mSmallerInternalDisplayUids;
private Display mCurrentDisplay;
- private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable;
-
private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -173,7 +141,6 @@
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
- mMediaArtworkProcessor.clearCache();
mMediaMetadata = metadata;
dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
}
@@ -184,13 +151,9 @@
*/
public NotificationMediaManager(
Context context,
- Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- MediaArtworkProcessor mediaArtworkProcessor,
- KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- @Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor,
@@ -199,12 +162,8 @@
WallpaperManager wallpaperManager,
DisplayManager displayManager) {
mContext = context;
- mMediaArtworkProcessor = mediaArtworkProcessor;
- mKeyguardBypassController = keyguardBypassController;
mMediaListeners = new ArrayList<>();
- mNotificationShadeWindowController = notificationShadeWindowController;
mVisibilityProvider = visibilityProvider;
- mMainExecutor = mainExecutor;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
@@ -476,7 +435,6 @@
}
private void clearCurrentMediaNotificationSession() {
- mMediaArtworkProcessor.clearCache();
mMediaMetadata = null;
if (mMediaController != null) {
if (DEBUG_MEDIA) {
@@ -494,9 +452,6 @@
public void onDisplayUpdated(Display display) {
Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
mCurrentDisplay = display;
- if (mWallapperDrawable != null) {
- mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays());
- }
Trace.endSection();
}
@@ -531,18 +486,13 @@
}
/**
- * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+ * Update media state of lockscreen media views and controllers .
*/
- public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+ public void updateMediaMetaData(boolean metaDataChanged) {
if (mIsLockscreenLiveWallpaperEnabled) return;
Trace.beginSection("CentralSurfaces#updateMediaMetaData");
- if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
- Trace.endSection();
- return;
- }
-
if (getBackDropView() == null) {
Trace.endSection();
return; // called too early
@@ -566,170 +516,14 @@
+ " state=" + mStatusBarStateController.getState());
}
- Bitmap artworkBitmap = null;
- if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {
- artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- if (artworkBitmap == null) {
- artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- }
- }
-
- // Process artwork on a background thread and send the resulting bitmap to
- // finishUpdateMediaMetaData.
- if (metaDataChanged) {
- for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
- task.cancel(true);
- }
- mProcessArtworkTasks.clear();
- }
- if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
- mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
- allowEnterAnimation).execute(artworkBitmap));
- } else {
- finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
+ mColorExtractor.setHasMediaArtwork(false);
+ if (mScrimController != null) {
+ mScrimController.setHasBackdrop(false);
}
Trace.endSection();
}
- private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
- @Nullable Bitmap bmp) {
- Drawable artworkDrawable = null;
- if (bmp != null) {
- artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
- }
- boolean hasMediaArtwork = artworkDrawable != null;
- boolean allowWhenShade = false;
- if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
- Bitmap lockWallpaper =
- mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
- if (lockWallpaper != null) {
- artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
- mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays());
- // We're in the SHADE mode on the SIM screen - yet we still need to show
- // the lockscreen wallpaper in that mode.
- allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
- }
- }
-
- NotificationShadeWindowController windowController =
- mNotificationShadeWindowController.get();
- boolean hideBecauseOccluded = mKeyguardStateController.isOccluded();
-
- final boolean hasArtwork = artworkDrawable != null;
- mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
- if (mScrimController != null) {
- mScrimController.setHasBackdrop(hasArtwork);
- }
-
- if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
- && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
- && mBiometricUnlockController != null && mBiometricUnlockController.getMode()
- != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
- && !hideBecauseOccluded) {
- // time to show some art!
- if (mBackdrop.getVisibility() != View.VISIBLE) {
- mBackdrop.setVisibility(View.VISIBLE);
- if (allowEnterAnimation) {
- mBackdrop.setAlpha(0);
- mBackdrop.animate().alpha(1f);
- } else {
- mBackdrop.animate().cancel();
- mBackdrop.setAlpha(1f);
- }
- if (windowController != null) {
- windowController.setBackdropShowing(true);
- }
- metaDataChanged = true;
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
- }
- }
- if (metaDataChanged) {
- if (mBackdropBack.getDrawable() != null) {
- Drawable drawable =
- mBackdropBack.getDrawable().getConstantState()
- .newDrawable(mBackdropFront.getResources()).mutate();
- mBackdropFront.setImageDrawable(drawable);
- mBackdropFront.setAlpha(1f);
- mBackdropFront.setVisibility(View.VISIBLE);
- } else {
- mBackdropFront.setVisibility(View.INVISIBLE);
- }
-
- if (DEBUG_MEDIA_FAKE_ARTWORK) {
- final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
- Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
- mBackdropBack.setBackgroundColor(0xFFFFFFFF);
- mBackdropBack.setImageDrawable(new ColorDrawable(c));
- } else {
- if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) {
- mWallapperDrawable =
- (LockscreenWallpaper.WallpaperDrawable) artworkDrawable;
- }
- mBackdropBack.setImageDrawable(artworkDrawable);
- }
-
- if (mBackdropFront.getVisibility() == View.VISIBLE) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
- + mBackdropFront.getDrawable()
- + " to "
- + mBackdropBack.getDrawable());
- }
- mBackdropFront.animate()
- .setDuration(250)
- .alpha(0f).withEndAction(mHideBackdropFront);
- }
- }
- } else {
- // need to hide the album art, either because we are unlocked, on AOD
- // or because the metadata isn't there to support it
- if (mBackdrop.getVisibility() != View.GONE) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
- }
- boolean cannotAnimateDoze = mStatusBarStateController.isDozing()
- && !ScrimState.AOD.getAnimateChange();
- if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode()
- == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
- || cannotAnimateDoze))
- || hideBecauseOccluded) {
- // We are unlocking directly - no animation!
- mBackdrop.setVisibility(View.GONE);
- mBackdropBack.setImageDrawable(null);
- if (windowController != null) {
- windowController.setBackdropShowing(false);
- }
- } else {
- if (windowController != null) {
- windowController.setBackdropShowing(false);
- }
- mBackdrop.animate()
- .alpha(0)
- .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
- .setDuration(300)
- .setStartDelay(0)
- .withEndAction(() -> {
- mBackdrop.setVisibility(View.GONE);
- mBackdropFront.animate().cancel();
- mBackdropBack.setImageDrawable(null);
- mMainExecutor.execute(mHideBackdropFront);
- });
- if (mKeyguardStateController.isKeyguardFadingAway()) {
- mBackdrop.animate()
- .setDuration(
- mKeyguardStateController.getShortenedFadingAwayDuration())
- .setStartDelay(
- mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setInterpolator(Interpolators.LINEAR)
- .start();
- }
- }
- }
- }
- }
-
public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
mBackdrop = backdrop;
@@ -758,15 +552,6 @@
}
};
- private Bitmap processArtwork(Bitmap artwork) {
- return mMediaArtworkProcessor.processArtwork(mContext, artwork);
- }
-
- @MainThread
- private void removeTask(AsyncTask<?, ?, ?> task) {
- mProcessArtworkTasks.remove(task);
- }
-
// TODO(b/273443374): remove
public boolean isLockscreenWallpaperOnNotificationShade() {
return mBackdrop != null && mLockscreenWallpaper != null
@@ -780,52 +565,6 @@
return mBackdrop;
}
- /**
- * {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
- */
- private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> {
-
- private final WeakReference<NotificationMediaManager> mManagerRef;
- private final boolean mMetaDataChanged;
- private final boolean mAllowEnterAnimation;
-
- ProcessArtworkTask(NotificationMediaManager manager, boolean changed,
- boolean allowAnimation) {
- mManagerRef = new WeakReference<>(manager);
- mMetaDataChanged = changed;
- mAllowEnterAnimation = allowAnimation;
- }
-
- @Override
- protected Bitmap doInBackground(Bitmap... bitmaps) {
- NotificationMediaManager manager = mManagerRef.get();
- if (manager == null || bitmaps.length == 0 || isCancelled()) {
- return null;
- }
- return manager.processArtwork(bitmaps[0]);
- }
-
- @Override
- protected void onPostExecute(@Nullable Bitmap result) {
- NotificationMediaManager manager = mManagerRef.get();
- if (manager != null && !isCancelled()) {
- manager.removeTask(this);
- manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result);
- }
- }
-
- @Override
- protected void onCancelled(Bitmap result) {
- if (result != null) {
- result.recycle();
- }
- NotificationMediaManager manager = mManagerRef.get();
- if (manager != null) {
- manager.removeTask(this);
- }
- }
- }
-
public interface MediaListener {
/**
* Called whenever there's new metadata or playback state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index d4b6dfb..61ebcc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar;
+
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
@@ -48,10 +49,10 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -535,7 +536,8 @@
public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {
if (isRemoteInputActive(entry)) {
entry.mRemoteEditImeVisible = false;
- mRemoteInputController.removeRemoteInput(entry, null);
+ mRemoteInputController.removeRemoteInput(entry, null,
+ /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f8c049e..23b697e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -133,7 +133,7 @@
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController hostLayoutController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -143,7 +143,7 @@
public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
NotificationRoundnessManager roundnessManager) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mAmbientState = ambientState;
mHostLayout = hostLayout;
mRoundnessManager = roundnessManager;
@@ -964,7 +964,7 @@
@Override
public void onStateChanged(int newState) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mStatusBarState = newState;
updateInteractiveness();
}
@@ -1022,17 +1022,17 @@
}
public void setController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mController = notificationShelfController;
}
public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mCanModifyColorOfNotifications = canModifyColorOfNotifications;
}
public void setCanInteract(boolean canInteract) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mCanInteract = canInteract;
updateInteractiveness();
}
@@ -1050,7 +1050,7 @@
}
public void requestRoundnessResetFor(ExpandableView child) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
child.requestRoundnessReset(SHELF_SCROLL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index d5e4902..17da015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -31,6 +31,8 @@
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Objects;
@@ -69,7 +71,8 @@
* @param entry the entry for which a remote input is now active.
* @param token a token identifying the view that is managing the remote input
*/
- public void addRemoteInput(NotificationEntry entry, Object token) {
+ public void addRemoteInput(NotificationEntry entry, Object token,
+ @CompileTimeConstant String reason) {
Objects.requireNonNull(entry);
Objects.requireNonNull(token);
boolean isActive = isRemoteInputActive(entry);
@@ -77,7 +80,9 @@
entry /* contains */, null /* remove */, token /* removeToken */);
mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,
isActive /* isRemoteInputAlreadyActive */,
- found /* isRemoteInputFound */);
+ found /* isRemoteInputFound */,
+ reason /* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
if (!found) {
mOpen.add(new Pair<>(new WeakReference<>(entry), token));
}
@@ -96,7 +101,8 @@
* the entry is only removed if the token matches the last added token for this
* entry. If null, the entry is removed regardless.
*/
- public void removeRemoteInput(NotificationEntry entry, Object token) {
+ public void removeRemoteInput(NotificationEntry entry, Object token,
+ @CompileTimeConstant String reason) {
Objects.requireNonNull(entry);
if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {
mLogger.logRemoveRemoteInput(
@@ -104,9 +110,12 @@
true /* remoteEditImeVisible */,
true /* remoteEditImeAnimatingAway */,
isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */,
- isRemoteInputActive() /* isRemoteInputActive */);
+ isRemoteInputActive() /* isRemoteInputActive */,
+ reason /* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
return;
}
+
// If the view is being removed, this may be called even though we're not active
boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
mLogger.logRemoveRemoteInput(
@@ -114,7 +123,9 @@
entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
- isRemoteInputActive()/* isRemoteInputActive */);
+ isRemoteInputActive()/* isRemoteInputActive */,
+ reason/* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
if (!remoteInputActiveForEntry) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7f5829d..125c8efe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,7 +31,6 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -45,12 +44,10 @@
import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -61,7 +58,6 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -71,7 +67,6 @@
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import dagger.Binds;
import dagger.Lazy;
@@ -122,13 +117,9 @@
@Provides
static NotificationMediaManager provideNotificationMediaManager(
Context context,
- Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- MediaArtworkProcessor mediaArtworkProcessor,
- KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- @Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor,
@@ -138,13 +129,9 @@
DisplayManager displayManager) {
return new NotificationMediaManager(
context,
- notificationShadeWindowController,
visibilityProvider,
- mediaArtworkProcessor,
- keyguardBypassController,
notifPipeline,
notifCollection,
- mainExecutor,
mediaDataManager,
statusBarStateController,
colorExtractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
deleted file mode 100644
index 732c115..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-
-import androidx.palette.graphics.Palette;
-
-import java.util.List;
-
-/**
- * A gutted class that now contains only a color extraction utility used by the
- * MediaArtworkProcessor, which has otherwise supplanted this.
- *
- * TODO(b/182926117): move this into MediaArtworkProcessor.kt
- */
-public class MediaNotificationProcessor {
-
- /**
- * The population fraction to select a white or black color as the background over a color.
- */
- private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
- private static final float BLACK_MAX_LIGHTNESS = 0.08f;
- private static final float WHITE_MIN_LIGHTNESS = 0.90f;
- private static final int RESIZE_BITMAP_AREA = 150 * 150;
-
- private MediaNotificationProcessor() {
- }
-
- /**
- * Finds an appropriate background swatch from media artwork.
- *
- * @param artwork Media artwork
- * @return Swatch that should be used as the background of the media notification.
- */
- public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) {
- return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate());
- }
-
- /**
- * Finds an appropriate background swatch from the palette of media artwork.
- *
- * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
- * @return Swatch that should be used as the background of the media notification.
- */
- public static Palette.Swatch findBackgroundSwatch(Palette palette) {
- // by default we use the dominant palette
- Palette.Swatch dominantSwatch = palette.getDominantSwatch();
- if (dominantSwatch == null) {
- return new Palette.Swatch(Color.WHITE, 100);
- }
-
- if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
- return dominantSwatch;
- }
- // Oh well, we selected black or white. Lets look at the second color!
- List<Palette.Swatch> swatches = palette.getSwatches();
- float highestNonWhitePopulation = -1;
- Palette.Swatch second = null;
- for (Palette.Swatch swatch : swatches) {
- if (swatch != dominantSwatch
- && swatch.getPopulation() > highestNonWhitePopulation
- && !isWhiteOrBlack(swatch.getHsl())) {
- second = swatch;
- highestNonWhitePopulation = swatch.getPopulation();
- }
- }
- if (second == null) {
- return dominantSwatch;
- }
- if (dominantSwatch.getPopulation() / highestNonWhitePopulation
- > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
- // The dominant swatch is very dominant, lets take it!
- // We're not filtering on white or black
- return dominantSwatch;
- } else {
- return second;
- }
- }
-
- /**
- * Generate a palette builder for media artwork.
- *
- * For producing a smooth background transition, the palette is extracted from only the left
- * side of the artwork.
- *
- * @param artwork Media artwork
- * @return Builder that generates the {@link Palette} for the media artwork.
- */
- public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
- // for the background we only take the left side of the image to ensure
- // a smooth transition
- return Palette.from(artwork)
- .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight())
- .clearFilters() // we want all colors, red / white / black ones too!
- .resizeBitmapArea(RESIZE_BITMAP_AREA);
- }
-
- private static boolean isWhiteOrBlack(float[] hsl) {
- return isBlack(hsl) || isWhite(hsl);
- }
-
- /**
- * @return true if the color represents a color which is close to black.
- */
- private static boolean isBlack(float[] hslColor) {
- return hslColor[2] <= BLACK_MAX_LIGHTNESS;
- }
-
- /**
- * @return true if the color represents a color which is close to white.
- */
- private static boolean isWhite(float[] hslColor) {
- return hslColor[2] >= WHITE_MIN_LIGHTNESS;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 39b999c..ff89c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -32,17 +32,24 @@
fun logAddRemoteInput(
entryKey: String,
isRemoteInputAlreadyActive: Boolean,
- isRemoteInputFound: Boolean
+ isRemoteInputFound: Boolean,
+ reason: String,
+ notificationStyle: String
) =
logBuffer.log(
TAG,
DEBUG,
{
str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
bool1 = isRemoteInputAlreadyActive
bool2 = isRemoteInputFound
},
- { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" }
+ {
+ "addRemoteInput reason:$str2 entry: $str1, style:$str3" +
+ ", isAlreadyActive: $bool1, isFound:$bool2"
+ }
)
/** logs removeRemoteInput invocation of [RemoteInputController] */
@@ -52,20 +59,25 @@
remoteEditImeVisible: Boolean,
remoteEditImeAnimatingAway: Boolean,
isRemoteInputActiveForEntry: Boolean,
- isRemoteInputActive: Boolean
+ isRemoteInputActive: Boolean,
+ reason: String,
+ notificationStyle: String
) =
logBuffer.log(
TAG,
DEBUG,
{
str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
bool1 = remoteEditImeVisible
bool2 = remoteEditImeAnimatingAway
bool3 = isRemoteInputActiveForEntry
bool4 = isRemoteInputActive
},
{
- "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" +
+ "removeRemoteInput reason: $str2 entry: $str1" +
+ ", style: $str3, remoteEditImeVisible: $bool1" +
", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" +
", isRemoteInputActive: $bool4"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index affd2d1..4573d59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -979,6 +979,19 @@
return mExpandAnimationRunning;
}
+ /**
+ * @return NotificationStyle
+ */
+ public String getNotificationStyle() {
+ if (isSummaryWithChildren()) {
+ return "summary";
+ }
+
+ final Class<? extends Notification.Style> style =
+ getSbn().getNotification().getNotificationStyle();
+ return style == null ? "nostyle" : style.getSimpleName();
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
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 50efbb5..eb5c1fa 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
@@ -167,17 +167,16 @@
NotificationShelfViewBinderWrapperControllerImpl.unsupported
override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.expectEnabled()) {
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- shelfIcons = icons
- }
+ if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
+ NotificationIconContainerViewBinder.bind(
+ icons,
+ shelfIconsViewModel,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ )
+ shelfIcons = icons
}
override fun onDensityOrFontScaleChanged(context: Context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9340b85..11c65e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1901,16 +1901,7 @@
return traceTag;
}
- if (isSummaryWithChildren()) {
- return traceTag + "(summary)";
- }
- Class<? extends Notification.Style> style =
- getEntry().getSbn().getNotification().getNotificationStyle();
- if (style == null) {
- return traceTag + "(nostyle)";
- } else {
- return traceTag + "(" + style.getSimpleName() + ")";
- }
+ return traceTag + "(" + getEntry().getNotificationStyle() + ")";
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a27a305..60e75ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1363,12 +1363,12 @@
result.mController.setPendingIntent(existingPendingIntent);
}
if (result.mController.updatePendingIntentFromActions(actions)) {
- if (!result.mView.isActive()) {
- result.mView.focus();
+ if (!result.mController.isActive()) {
+ result.mController.focus();
}
} else {
- if (result.mView.isActive()) {
- result.mView.close();
+ if (result.mController.isActive()) {
+ result.mController.close();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 79f8f22..dba93d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2735,7 +2735,7 @@
* @param listener callback for notification removed
*/
public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mOnNotificationRemovedListener = listener;
}
@@ -4982,12 +4982,12 @@
@Nullable
public ExpandableView getShelf() {
- if (!mShelfRefactor.expectEnabled()) return null;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null;
return mShelf;
}
public void setShelf(NotificationShelf shelf) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
@@ -5001,7 +5001,7 @@
}
public void setShelfController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5020780..6a70815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1431,7 +1431,7 @@
}
public void setShelfController(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mView.setShelfController(notificationShelfController);
}
@@ -1644,12 +1644,12 @@
}
public void setShelf(NotificationShelf shelf) {
- if (!mShelfRefactor.expectEnabled()) return;
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
mView.setShelf(shelf);
}
public int getShelfHeight() {
- if (!mShelfRefactor.expectEnabled()) {
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) {
return 0;
}
ExpandableView shelf = mView.getShelf();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 2809cad..fb45a67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -498,8 +498,7 @@
case MODE_WAKE_AND_UNLOCK:
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
- mMediaManager.updateMediaMetaData(false /* metaDataChanged */,
- true /* allowEnterAnimation */);
+ mMediaManager.updateMediaMetaData(false /* metaDataChanged */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 5553270..f9856b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -205,14 +205,13 @@
}
public void setupShelf(NotificationShelfController notificationShelfController) {
- mShelfRefactor.assertDisabled();
+ mShelfRefactor.assertInLegacyMode();
mShelfIcons = notificationShelfController.getShelfIcons();
}
public void setShelfIcons(NotificationIconContainer icons) {
- if (mShelfRefactor.expectEnabled()) {
- mShelfIcons = icons;
- }
+ if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
+ mShelfIcons = icons;
}
public void onDensityOrFontScaleChanged(@NotNull Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 92c786f..00fd9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -263,8 +263,7 @@
if (result.success) {
mCached = true;
mCache = result.bitmap;
- mMediaManager.updateMediaMetaData(
- true /* metaDataChanged */, true /* allowEnterAnimation */);
+ mMediaManager.updateMediaMetaData(true /* metaDataChanged */);
}
mLoader = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3adf338..400ac7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -961,7 +961,7 @@
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
if (isShowing) {
- mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
+ mMediaManager.updateMediaMetaData(false);
}
mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 2d14f6b..57a8e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -221,7 +221,7 @@
@Override
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
- mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
+ mMediaManager.updateMediaMetaData(metaDataChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 2c15e27..de37170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -34,6 +34,8 @@
import com.android.systemui.util.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
/**
* When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -65,7 +67,8 @@
private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
private val interactionJankMonitor: InteractionJankMonitor,
private val powerManager: PowerManager,
- private val handler: Handler = Handler()
+ private val handler: Handler = Handler(),
+ private val featureFlags: FeatureFlags,
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
private lateinit var shadeViewController: ShadeViewController
@@ -285,7 +288,11 @@
// up, with unpredictable consequences.
if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
shouldAnimateInKeyguard) {
- aodUiAnimationPlaying = true
+ if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ // Tracking this state should no longer be relevant, as the isInteractive
+ // check covers it
+ aodUiAnimationPlaying = true
+ }
// Show AOD. That'll cause the KeyguardVisibilityHelper to call
// #animateInKeyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 62e2381..b614b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -38,6 +38,7 @@
import com.android.systemui.res.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.user.UserSwitchDialogController;
@@ -148,6 +149,7 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -160,7 +162,8 @@
mStatusBarStateController = statusBarStateController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
- screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+ screenOffAnimationController, /* animateYPos= */ false,
+ featureFlags, /* logBuffer= */ null);
mUserSwitchDialogController = userSwitchDialogController;
mUiEventLogger = uiEventLogger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index bb074ac..dfe2686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -38,10 +38,11 @@
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -160,6 +161,7 @@
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
+ FeatureFlags featureFlags,
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardUserSwitcherView);
@@ -174,7 +176,8 @@
mUserSwitcherController, this);
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
- screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
+ screenOffAnimationController, /* animateYPos= */ false,
+ featureFlags, /* logBuffer= */ null);
mBackground = new KeyguardUserSwitcherScrim(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 53fed3d..ceed81a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -305,7 +305,8 @@
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
// Pass null to ensure all inputs are cleared for this entry b/227115380
- mController.removeRemoteInput(mEntry, null);
+ mController.removeRemoteInput(mEntry, null,
+ /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
}
}
}
@@ -426,7 +427,7 @@
@VisibleForTesting
void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) {
- mController.removeRemoteInput(mEntry, mToken);
+ mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus");
mEntry.remoteInputText = mEditText.getText();
// During removal, we get reattached and lose focus. Not hiding in that
@@ -536,7 +537,8 @@
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
- mController.removeRemoteInput(mEntry, mToken);
+ mController.removeRemoteInput(mEntry, mToken,
+ /* reason= */"RemoteInputView#onDetachedFromWindow");
mController.removeSpinning(mEntry.getKey(), mToken);
}
@@ -655,7 +657,7 @@
mEditText.setText(mEntry.remoteInputText);
mEditText.setSelection(mEditText.length());
mEditText.requestFocus();
- mController.addRemoteInput(mEntry, mToken);
+ mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus");
setAttachment(mEntry.remoteInputAttachment);
updateSendButton();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index a50fd6f..6c0d433 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -255,7 +255,8 @@
entry.lastRemoteInputSent = SystemClock.elapsedRealtime()
entry.mRemoteEditImeAnimatingAway = true
remoteInputController.addSpinning(entry.key, view.mToken)
- remoteInputController.removeRemoteInput(entry, view.mToken)
+ remoteInputController.removeRemoteInput(entry, view.mToken,
+ /* reason= */ "RemoteInputViewController#sendRemoteInput")
remoteInputController.remoteInputSent(entry)
entry.setHasSentReply()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6afa525..4d8768f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,9 +25,11 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
@@ -47,8 +49,8 @@
import java.util.TimeZone
import java.util.concurrent.Executor
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.yield
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -90,10 +92,10 @@
@Mock private lateinit var smallClockEvents: ClockFaceEvents
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
- @Mock private lateinit var transitionRepository: KeyguardTransitionRepository
private lateinit var repository: FakeKeyguardRepository
@Mock private lateinit var smallLogBuffer: LogBuffer
@Mock private lateinit var largeLogBuffer: LogBuffer
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: ClockEventController
@Before
@@ -125,17 +127,13 @@
withDeps.featureFlags.apply {
set(Flags.REGION_SAMPLING, false)
- set(Flags.DOZING_MIGRATION_1, false)
+ set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
set(Flags.FACE_AUTH_REFACTOR, false)
}
underTest =
ClockEventController(
withDeps.keyguardInteractor,
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- featureFlags = withDeps.featureFlags,
- )
- .keyguardTransitionInteractor,
+ keyguardTransitionInteractor,
broadcastDispatcher,
batteryController,
keyguardUpdateMonitor,
@@ -316,6 +314,68 @@
}
@Test
+ fun listenForDozeAmountTransition_updatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep)
+
+ val job = underTest.listenForDozeAmountTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.4f
+ )
+ yield()
+
+ verify(animations, times(2)).doze(0.4f)
+
+ job.cancel()
+ }
+
+ @Test
+ fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToAodTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, times(2)).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
+ fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToAodTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, never()).doze(1f)
+
+ job.cancel()
+ }
+
+ @Test
fun unregisterListeners_validate() =
runBlocking(IMMEDIATE) {
underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 3b8e02f..22c75d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +33,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.statusbar.notification.AnimatableProperty;
@@ -60,6 +63,7 @@
@Mock protected FeatureFlags mFeatureFlags;
@Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
+ @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakePowerRepository mFakePowerRepository;
@@ -90,6 +94,7 @@
mFeatureFlags,
mInteractionJankMonitor,
deps.getKeyguardInteractor(),
+ mKeyguardTransitionInteractor,
mDumpManager,
PowerInteractorFactory.create(
mFakePowerRepository
@@ -105,8 +110,8 @@
};
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+ when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
}
protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..5346db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -19,6 +19,9 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -87,6 +90,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
MockitoAnnotations.initMocks(this);
mContextWrapper = new ContextWrapper(mContext) {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index fd258e3..3b2ea0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -56,6 +59,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
mock(SecureSettings.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 3a8bcd0..76a3153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
@@ -75,6 +78,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
@@ -96,6 +100,7 @@
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
mLastIsMoveToTucked);
mEndListenerCaptor.getAllValues().clear();
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
index 9b81947..83bcd8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
@@ -43,6 +46,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 5764839..e01f1b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -19,6 +19,9 @@
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
@@ -72,6 +75,7 @@
@Before
public void setUp() {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 98be49f..a88ee10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -18,6 +18,9 @@
import static android.view.View.OVER_SCROLL_NEVER;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -33,6 +36,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
@@ -79,6 +83,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
mock(SecureSettings.class));
@@ -213,5 +218,6 @@
@After
public void tearDown() {
mMotionEventHelper.recycleEvents();
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 31824ec..41e5c20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -19,6 +19,9 @@
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
@@ -73,6 +76,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final WindowManager wm = mContext.getSystemService(WindowManager.class);
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 5bb5e01..b0776c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -22,7 +22,9 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +52,7 @@
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
+import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -110,6 +113,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
final Rect mDisplayBounds = new Rect();
mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
DISPLAY_WINDOW_HEIGHT);
@@ -145,6 +149,7 @@
UserHandle.USER_CURRENT);
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
+ mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);
mMenuViewLayer.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 5cd0fd0..ac2bfaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -18,6 +18,9 @@
import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -67,6 +70,7 @@
@Before
public void setUp() throws Exception {
+ setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 3df9cbb..91409a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -11,11 +11,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
+import com.android.systemui.communal.shared.CommunalContentSize
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -59,9 +62,12 @@
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
+ private lateinit var communalRepository: FakeCommunalRepository
+
private lateinit var logBuffer: LogBuffer
private val testDispatcher = StandardTestDispatcher()
+
private val testScope = TestScope(testDispatcher)
@Before
@@ -71,6 +77,14 @@
logBuffer = FakeLogBuffer.Factory.create()
featureFlagEnabled(true)
+ communalRepository = FakeCommunalRepository()
+ communalRepository.setIsCommunalEnabled(true)
+
+ overrideResource(
+ R.array.config_communalWidgetAllowlist,
+ arrayOf(componentName1, componentName2)
+ )
+
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(userTracker.userHandle).thenReturn(userHandle)
}
@@ -219,11 +233,36 @@
Mockito.verify(appWidgetHost).stopListening()
}
+ @Test
+ fun getCommunalWidgetAllowList_onInit() {
+ testScope.runTest {
+ val repository = initCommunalWidgetRepository()
+ val communalWidgetAllowlist = repository.communalWidgetAllowlist
+ assertThat(
+ listOf(
+ CommunalWidgetMetadata(
+ componentName = componentName1,
+ priority = 2,
+ sizes = listOf(CommunalContentSize.HALF)
+ ),
+ CommunalWidgetMetadata(
+ componentName = componentName2,
+ priority = 1,
+ sizes = listOf(CommunalContentSize.HALF)
+ )
+ )
+ )
+ .containsExactly(*communalWidgetAllowlist.toTypedArray())
+ }
+ }
+
private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
return CommunalWidgetRepositoryImpl(
+ context,
appWidgetManager,
appWidgetHost,
broadcastDispatcher,
+ communalRepository,
packageManager,
userManager,
userTracker,
@@ -282,4 +321,9 @@
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
+
+ companion object {
+ const val componentName1 = "component name 1"
+ const val componentName2 = "component name 2"
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
new file mode 100644
index 0000000..bb6786a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.flags
+
+import android.platform.test.flag.junit.SetFlagsRule
+
+fun SetFlagsRule.setFlagDefault(flagName: String) {
+ if (getFlagDefault(flagName)) {
+ enableFlags(flagName)
+ } else {
+ disableFlags(flagName)
+ }
+}
+
+// NOTE: This code uses reflection to gain access to private members of aconfig generated
+// classes (in the same way SetFlagsRule does internally) because this is the only way to get
+// at the underlying information and read the current value of the flag.
+// If aconfig had flag constants with accessible default values, this would be unnecessary.
+private fun getFlagDefault(name: String): Boolean {
+ val flagPackage = name.substringBeforeLast(".")
+ val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl")
+ val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance()
+ val flagMethodName = name.substringAfterLast(".").snakeToCamelCase()
+ val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName)
+ return flagGetter.invoke(featureFlagsImpl) as Boolean
+}
+
+private fun String.snakeToCamelCase(): String {
+ val pattern = "_[a-z]".toRegex()
+ return replace(pattern) { it.value.last().uppercase() }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 0ee348e..7750d25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import kotlin.math.max
+import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -149,26 +151,52 @@
}
@Test
- fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
- }
+ fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+ val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+ // Given a second slider progress event smaller than the progress threshold
+ val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
// GIVEN system running for 1s
clock.advanceTime(1000)
- // WHEN two calls to play occur with the required threshold separation
- sliderHapticFeedbackProvider.onProgress(progress)
+ // WHEN two calls to play occur with the required threshold separation (time and progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
clock.advanceTime(dragTextureThresholdMillis.toLong())
- sliderHapticFeedbackProvider.onProgress(progress)
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
- // THEN the correct composition plays two times
- verify(vibratorHelper, times(2))
- .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
+ // THEN Only the first compositions plays
+ verify(vibratorHelper, times(1))
+ .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
+ verify(vibratorHelper, times(1))
+ .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java))
+ }
+
+ @Test
+ fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+ val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+ // Given a second slider progress event beyond progress threshold
+ val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+ val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
+
+ // GIVEN system running for 1s
+ clock.advanceTime(1000)
+
+ // WHEN two calls to play occur with the required threshold separation (time and progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
+ clock.advanceTime(dragTextureThresholdMillis.toLong())
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+ // THEN the correct compositions play
+ verify(vibratorHelper, times(1))
+ .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
+ verify(vibratorHelper, times(1))
+ .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))
}
@Test
@@ -229,6 +257,38 @@
.vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
}
+ fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
+
+ // GIVEN system running for 1s
+ clock.advanceTime(1000)
+
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // THEN the dragTextureLastProgress remembers the latest progress
+ assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
+
+ @Test
+ fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
+
+ // GIVEN system running for 1s
+ clock.advanceTime(1000)
+
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // WHEN the handle is released
+ sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+
+ // THEN the dragTextureLastProgress tracker is reset
+ assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
+
private fun scaleAtBookends(velocity: Float): Float {
val range = config.upperBookendScale - config.lowerBookendScale
val interpolatedVelocity =
@@ -244,4 +304,15 @@
val bump = interpolatedVelocity * config.additionalVelocityMaxBump
return interpolatedProgress * range + config.progressBasedDragMinScale + bump
}
+
+ private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ scaleAtProgressChange(velocity, progress)
+ )
+ }
+ return ticks.compose()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index d8cdf29..90fd652 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -147,7 +147,7 @@
emptyMap()
)
verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
- verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
+ verify(authController, times(2)).addCallback(authControllerCallback.capture())
}
@Test
@@ -314,18 +314,18 @@
fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
testScope.runTest {
createBiometricSettingsRepository()
+ val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
faceAuthIsEnabledByBiometricManager()
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
runCurrent()
- clearInvocations(authController)
- whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
- val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ enrollmentChange(FACE, PRIMARY_USER_ID, false)
assertThat(faceAuthAllowed()).isFalse()
- verify(authController).addCallback(authControllerCallback.capture())
+
enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
assertThat(faceAuthAllowed()).isFalse()
@@ -375,25 +375,20 @@
fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
testScope.runTest {
createBiometricSettingsRepository()
+ val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
userRepository.setSelectedUserInfo(ANOTHER_USER)
doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
-
- runCurrent()
- clearInvocations(authController)
-
- val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- runCurrent()
-
- verify(authController).addCallback(authControllerCallback.capture())
-
+ onNonStrongAuthChanged(true, ANOTHER_USER_ID)
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
+ runCurrent()
- assertThat(faceAuthAllowed()).isTrue()
+ assertThat(faceAuthAllowed).isTrue()
}
@Test
@@ -637,6 +632,7 @@
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
@@ -660,6 +656,7 @@
val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
faceAuthIsEnrolled()
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsNonStrongBiometric()
@@ -753,7 +750,9 @@
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
- authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
+ authControllerCallback.allValues.forEach {
+ it.onEnrollmentsChanged(biometricType, userId, enabled)
+ }
}
private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 06eb0dd..6ad2d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -43,7 +43,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -95,6 +95,7 @@
FakeDeviceEntryFingerprintAuthRepository
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var powerInteractor: PowerInteractor
+ private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig
@@ -123,6 +124,8 @@
facePropertyRepository = FakeFacePropertyRepository()
fakeKeyguardRepository = FakeKeyguardRepository()
powerInteractor = PowerInteractorFactory.create().powerInteractor
+ fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
+
underTest =
SystemUIKeyguardFaceAuthInteractor(
mContext,
@@ -147,7 +150,7 @@
mock(StatusBarStateController::class.java),
mock(KeyguardStateController::class.java),
bouncerRepository,
- mock(BiometricSettingsRepository::class.java),
+ fakeBiometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
),
@@ -160,6 +163,7 @@
facePropertyRepository,
faceWakeUpTriggersConfig,
powerInteractor,
+ fakeBiometricSettingsRepository,
)
}
@@ -481,6 +485,7 @@
fun faceUnlockIsDisabledWhenFpIsLockedOut() =
testScope.runTest {
underTest.start()
+ fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..255f4df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GoneToAodTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: GoneToAodTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
+ repository = FakeKeyguardTransitionRepository()
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest = GoneToAodTransitionViewModel(interactor)
+ }
+
+ @Test
+ fun enterFromTopTranslationY() =
+ testScope.runTest {
+ val pixels = -100f
+ val enterFromTopTranslationY by
+ collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt()))
+
+ // The animation should only start > halfway through
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+
+ // At the end, the translation should be complete and set to zero
+ repository.sendTransitionStep(step(1f))
+ assertThat(enterFromTopTranslationY).isEqualTo(0f)
+ }
+
+ @Test
+ fun enterFromTopAnimationAlpha() =
+ testScope.runTest {
+ val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha)
+
+ // The animation should only start > halfway through
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(.85f))
+ assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f))
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(enterFromTopAnimationAlpha).isEqualTo(1f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "GoneToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 71688db..4f545cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -26,12 +28,17 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.ClockController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -41,6 +48,7 @@
import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
@SmallTest
@@ -51,10 +59,20 @@
private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var configurationRepository: FakeConfigurationRepository
@Mock private lateinit var burnInInteractor: BurnInInteractor
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+ @Mock
+ private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
private val burnInFlow = MutableStateFlow(BurnInModel())
+ private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0)
+ private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+ private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+ private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1)
+ private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE)
@Before
fun setUp() {
@@ -71,9 +89,30 @@
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
keyguardInteractor = withDeps.keyguardInteractor
repository = withDeps.repository
+ configurationRepository = withDeps.configurationRepository
+
+ whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+ .thenReturn(emptyFlow<Float>())
+ whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+ .thenReturn(enterFromTopAnimationAlpha)
whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
- underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor)
+
+ whenever(keyguardTransitionInteractor.goneToAodTransition)
+ .thenReturn(goneToAodTransitionStep)
+ whenever(keyguardTransitionInteractor.dozeAmountTransition)
+ .thenReturn(dozeAmountTransitionStep)
+ whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState)
+
+ underTest =
+ KeyguardRootViewModel(
+ context,
+ keyguardInteractor,
+ burnInInteractor,
+ goneToAodTransitionViewModel,
+ aodToLockscreenTransitionViewModel,
+ keyguardTransitionInteractor,
+ )
underTest.clockControllerProvider = Provider { clockController }
}
@@ -118,7 +157,7 @@
val scale by collectLastValue(underTest.scale)
// Set to not dozing (on lockscreen)
- repository.setDozeAmount(0f)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 0f))
// Trigger a change to the burn-in model
burnInFlow.value =
@@ -141,8 +180,7 @@
val scale by collectLastValue(underTest.scale)
// Set to dozing (on AOD)
- repository.setDozeAmount(1f)
-
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
burnInFlow.value =
BurnInModel(
@@ -150,10 +188,15 @@
translationY = 30,
scale = 0.5f,
)
-
assertThat(translationX).isEqualTo(20)
assertThat(translationY).isEqualTo(30)
assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+
+ // Set to the beginning of GONE->AOD transition
+ goneToAodTransitionStep.emit(TransitionStep(value = 0f))
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
}
@Test
@@ -166,7 +209,7 @@
val scale by collectLastValue(underTest.scale)
// Set to dozing (on AOD)
- repository.setDozeAmount(1f)
+ dozeAmountTransitionStep.emit(TransitionStep(value = 1f))
// Trigger a change to the burn-in model
burnInFlow.value =
@@ -180,4 +223,28 @@
assertThat(translationY).isEqualTo(0)
assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
}
+
+ @Test
+ fun burnInLayerVisibility() =
+ testScope.runTest {
+ val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+ startedKeyguardState.value = KeyguardState.OCCLUDED
+ assertThat(burnInLayerVisibility).isNull()
+
+ startedKeyguardState.value = KeyguardState.AOD
+ assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun burnInLayerAlpha() =
+ testScope.runTest {
+ val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
+
+ enterFromTopAnimationAlpha.value = 0.2f
+ assertThat(burnInLayerAlpha).isEqualTo(0.2f)
+
+ enterFromTopAnimationAlpha.value = 1f
+ assertThat(burnInLayerAlpha).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 34360d2..6cdf4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -4,7 +4,9 @@
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -20,6 +22,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@RunWith(AndroidTestingRunner::class)
@@ -37,10 +40,11 @@
private val view: MediaProjectionAppSelectorView = mock()
private val policyResolver: ScreenCaptureDevicePolicyResolver = mock()
+ private val logger = mock<MediaProjectionMetricsLogger>()
private val thumbnailLoader = FakeThumbnailLoader()
- private val controller =
+ private fun createController(isFirstStart: Boolean = true) =
MediaProjectionAppSelectorController(
taskListProvider,
view,
@@ -50,6 +54,8 @@
appSelectorComponentName,
callerPackageName,
thumbnailLoader,
+ isFirstStart,
+ logger
)
@Before
@@ -61,7 +67,7 @@
fun initNoRecentTasks_bindsEmptyList() {
taskListProvider.tasks = emptyList()
- controller.init()
+ createController().init()
verify(view).bind(emptyList())
}
@@ -70,7 +76,7 @@
fun initOneRecentTask_bindsList() {
taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
- controller.init()
+ createController().init()
verify(view).bind(listOf(createRecentTask(taskId = 1)))
}
@@ -86,7 +92,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)
}
@@ -101,7 +107,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -124,7 +130,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -147,7 +153,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -172,7 +178,7 @@
)
taskListProvider.tasks = tasks
- controller.init()
+ createController().init()
verify(view)
.bind(
@@ -199,11 +205,29 @@
taskListProvider.tasks = tasks
givenCaptureAllowed(isAllow = false)
- controller.init()
+ createController().init()
verify(view).bind(emptyList())
}
+ @Test
+ fun init_firstStart_logsAppSelectorDisplayed() {
+ val controller = createController(isFirstStart = true)
+
+ controller.init()
+
+ verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
+ @Test
+ fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
+ val controller = createController(isFirstStart = false)
+
+ controller.init()
+
+ verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ }
+
private fun givenCaptureAllowed(isAllow: Boolean) {
whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)
}
@@ -216,6 +240,7 @@
): RecentTask {
return RecentTask(
taskId = taskId,
+ displayId = 0,
topActivityComponent = topActivityComponent,
baseIntentComponent = ComponentName("com", "Test"),
userId = userId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 2c7ee56..d75553f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -128,6 +128,7 @@
private fun createRecentTask(taskId: Int): RecentTask =
RecentTask(
taskId = taskId,
+ displayId = 0,
userId = 0,
topActivityComponent = null,
baseIntentComponent = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6e6833d..90d2e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,8 +17,10 @@
package com.android.systemui.screenrecord;
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -37,6 +39,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
@@ -76,6 +80,8 @@
private ActivityStarter mActivityStarter;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
@@ -86,8 +92,15 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mFeatureFlags = new FakeFeatureFlags();
- mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
- mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
+ mController = new RecordingController(
+ mMainExecutor,
+ mBroadcastDispatcher,
+ mContext,
+ mFeatureFlags,
+ mUserContextProvider,
+ () -> mDevicePolicyResolver,
+ mUserTracker,
+ mMediaProjectionMetricsLogger);
mController.addCallback(mCallback);
}
@@ -269,4 +282,21 @@
assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
}
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+ mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ verify(mMediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 0d694ee..7e41745 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -28,7 +28,6 @@
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -47,7 +46,6 @@
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val policy = FakeScreenshotPolicy()
- private val flags = FakeFeatureFlags()
/** Tests the Java-compatible function wrapper, ensures callback is invoked. */
@Test
@@ -58,7 +56,7 @@
.setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
.build()
)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
var result: ScreenshotData? = null
var callbackCount = 0
@@ -86,7 +84,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -111,7 +109,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
val processedData = processor.process(ScreenshotData.fromRequest(request))
@@ -138,7 +136,7 @@
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
Assert.assertThrows(IllegalStateException::class.java) {
runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
@@ -148,7 +146,7 @@
@Test
fun testProvidedImageScreenshot() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
policy.setManagedProfile(USER_ID, false)
@@ -173,7 +171,7 @@
@Test
fun testProvidedImageScreenshot_managedProfile() = runBlocking {
val bounds = Rect(50, 50, 150, 150)
- val processor = RequestProcessor(imageCapture, policy, flags, scope)
+ val processor = RequestProcessor(imageCapture, policy, scope)
// Indicate that the screenshot belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index d8821aa..3dc9037 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -43,8 +44,11 @@
private val controller0 = mock<ScreenshotController>()
private val controller1 = mock<ScreenshotController>()
+ private val notificationsController0 = mock<ScreenshotNotificationsController>()
+ private val notificationsController1 = mock<ScreenshotNotificationsController>()
private val controllerFactory = mock<ScreenshotController.Factory>()
private val callback = mock<TakeScreenshotService.RequestCallback>()
+ private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
private val fakeDisplayRepository = FakeDisplayRepository()
private val requestProcessor = FakeRequestProcessor()
@@ -59,12 +63,15 @@
testScope,
requestProcessor,
eventLogger,
+ notificationControllerFactory
)
@Before
fun setUp() {
whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)
whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1)
+ whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0)
+ whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)
}
@Test
@@ -310,6 +317,123 @@
screenshotExecutor.onDestroy()
}
+ @Test
+ fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessor_logsUiError() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ val onSaved = { _: Uri -> }
+ requestProcessor.shouldThrowException = true
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_reportsError() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ val screenshotRequested =
+ eventLogger.logs.filter {
+ it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id
+ }
+ assertThat(screenshotRequested).hasSize(2)
+ screenshotExecutor.onDestroy()
+ }
+
+ @Test
+ fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+ val onSaved = { _: Uri -> }
+ whenever(controller0.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+ whenever(controller1.handleScreenshot(any(), any(), any()))
+ .thenThrow(IllegalStateException::class.java)
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+
+ verify(notificationsController0).notifyScreenshotError(any())
+ verify(notificationsController1).notifyScreenshotError(any())
+ screenshotExecutor.onDestroy()
+ }
+
private suspend fun TestScope.setDisplays(vararg displays: Display) {
fakeDisplayRepository.emit(displays.toSet())
runCurrent()
@@ -328,8 +452,9 @@
private class FakeRequestProcessor : ScreenshotRequestProcessor {
var processed: ScreenshotData? = null
var toReturn: ScreenshotData? = null
-
+ var shouldThrowException = false
override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+ if (shouldThrowException) throw RequestProcessorException("")
processed = screenshot
return toReturn ?: screenshot
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 5091a70..f3809aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -65,6 +65,7 @@
private val requestProcessor = mock<RequestProcessor>()
private val devicePolicyManager = mock<DevicePolicyManager>()
private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>()
+ private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()
private val notificationsController = mock<ScreenshotNotificationsController>()
private val callback = mock<RequestCallback>()
@@ -87,6 +88,7 @@
.thenReturn(false)
whenever(userManager.isUserUnlocked).thenReturn(true)
whenever(controllerFactory.create(any(), any())).thenReturn(controller)
+ whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
@@ -323,7 +325,7 @@
userManager,
devicePolicyManager,
eventLogger,
- notificationsController,
+ notificationsControllerFactory,
mContext,
Runnable::run,
flags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 4f0cec5..6223e25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -390,6 +390,7 @@
mFeatureFlags,
mInteractionJankMonitor,
mKeyguardInteractor,
+ mKeyguardTransitionInteractor,
mDumpManager,
mPowerInteractor));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
deleted file mode 100644
index e4da53a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar
-
-import com.google.common.truth.Truth.assertThat
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Point
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-private const val WIDTH = 200
-private const val HEIGHT = 200
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class MediaArtworkProcessorTest : SysuiTestCase() {
-
- private var screenWidth = 0
- private var screenHeight = 0
-
- private lateinit var processor: MediaArtworkProcessor
-
- @Before
- fun setUp() {
- processor = MediaArtworkProcessor()
-
- val point = Point()
- checkNotNull(context.display).getSize(point)
- screenWidth = point.x
- screenHeight = point.y
- }
-
- @After
- fun tearDown() {
- processor.clearCache()
- }
-
- @Test
- fun testProcessArtwork() {
- // GIVEN some "artwork", which is just a solid blue image
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
- Canvas(artwork).drawColor(Color.BLUE)
- // WHEN the background is created from the artwork
- val background = processor.processArtwork(context, artwork)!!
- // THEN the background has the size of the screen that has been downsamples
- assertThat(background.height).isLessThan(screenHeight)
- assertThat(background.width).isLessThan(screenWidth)
- assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
- }
-
- @Test
- fun testCache() {
- // GIVEN a solid blue image
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
- Canvas(artwork).drawColor(Color.BLUE)
- // WHEN the background is processed twice
- val background1 = processor.processArtwork(context, artwork)!!
- val background2 = processor.processArtwork(context, artwork)!!
- // THEN the two bitmaps are the same
- // Note: This is currently broken and trying to use caching causes issues
- assertThat(background1).isNotSameInstanceAs(background2)
- }
-
- @Test
- fun testConfig() {
- // GIVEN some which is not ARGB_8888
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8)
- Canvas(artwork).drawColor(Color.BLUE)
- // WHEN the background is created from the artwork
- val background = processor.processArtwork(context, artwork)!!
- // THEN the background has Config ARGB_8888
- assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
- }
-
- @Test
- fun testRecycledArtwork() {
- // GIVEN some "artwork", which is just a solid blue image
- val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
- Canvas(artwork).drawColor(Color.BLUE)
- // AND the artwork is recycled
- artwork.recycle()
- // WHEN the background is created from the artwork
- val background = processor.processArtwork(context, artwork)
- // THEN the processed bitmap is null
- assertThat(background).isNull()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
index 9d6ea85..cfcf425 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -48,9 +48,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- doCallRealMethod()
- .whenever(notificationMediaManager)
- .updateMediaMetaData(anyBoolean(), anyBoolean())
+ doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean())
doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView
}
@@ -62,7 +60,7 @@
notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true
for (metaDataChanged in listOf(true, false)) {
for (allowEnterAnimation in listOf(true, false)) {
- notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation)
+ notificationMediaManager.updateMediaMetaData(metaDataChanged)
verify(notificationMediaManager, never()).mediaMetadata
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
deleted file mode 100644
index aeb5b03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.palette.graphics.Palette;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class MediaNotificationProcessorTest extends SysuiTestCase {
-
- private static final int BITMAP_WIDTH = 10;
- private static final int BITMAP_HEIGHT = 10;
-
- /**
- * Color tolerance is borrowed from the AndroidX test utilities for Palette.
- */
- private static final int COLOR_TOLERANCE = 8;
-
- @Nullable private Bitmap mArtwork;
-
- @After
- public void tearDown() {
- if (mArtwork != null) {
- mArtwork.recycle();
- mArtwork = null;
- }
- }
-
- @Test
- public void findBackgroundSwatch_white() {
- // Given artwork that is completely white.
- mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(mArtwork);
- canvas.drawColor(Color.WHITE);
- // WHEN the background swatch is computed
- Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
- // THEN the swatch color is white
- assertCloseColors(swatch.getRgb(), Color.WHITE);
- }
-
- @Test
- public void findBackgroundSwatch_red() {
- // Given artwork that is completely red.
- mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(mArtwork);
- canvas.drawColor(Color.RED);
- // WHEN the background swatch is computed
- Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
- // THEN the swatch color is red
- assertCloseColors(swatch.getRgb(), Color.RED);
- }
-
- static void assertCloseColors(int expected, int actual) {
- assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual));
- assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
- assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index e6f8c48..9aafee4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
@@ -80,10 +82,14 @@
@Mock
private lateinit var handler: Handler
+ private lateinit var featureFlags: FakeFeatureFlags
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ featureFlags = FakeFeatureFlags().apply {
+ set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
+ }
controller = UnlockedScreenOffAnimationController(
context,
wakefulnessLifecycle,
@@ -95,7 +101,8 @@
dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
interactionJankMonitor,
powerManager,
- handler = handler
+ handler = handler,
+ featureFlags,
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index 1250228..d33806e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy
+import com.android.systemui.flags.FakeFeatureFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -77,11 +78,13 @@
private lateinit var view: FrameLayout
private lateinit var testableLooper: TestableLooper
private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ featureFlags = FakeFeatureFlags()
view = LayoutInflater.from(context)
.inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -98,6 +101,7 @@
dozeParameters,
screenOffAnimationController,
userSwitchDialogController,
+ featureFlags,
uiEventLogger)
ViewUtils.attachView(view)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index f7e0120..6ef812b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui;
+import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -25,6 +27,7 @@
import android.os.Looper;
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
import android.testing.LeakCheck;
import android.testing.TestWithLooperRule;
@@ -68,6 +71,9 @@
new AndroidXAnimatorIsolationRule();
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), getLeakCheck());
@Rule
@@ -88,6 +94,10 @@
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
+ // Set the value of a single gantry flag inside the com.android.systemui package to
+ // ensure all flags in that package are faked (and thus require a value to be set).
+ mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG);
+
mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext);
mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(
mContext,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 10b284a..5dcc742 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -47,7 +47,7 @@
}
override fun getDimensionPixelSize(id: Int): Int {
- throw IllegalStateException("Don't use for tests")
+ return 0
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 1a8c583..30132f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,5 +1,6 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalWidgetMetadata
import com.android.systemui.communal.shared.CommunalAppWidgetInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -8,6 +9,7 @@
class FakeCommunalWidgetRepository : CommunalWidgetRepository {
private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null)
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
+ override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
_stopwatchAppWidgetInfo.value = appWidgetInfo
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index e91e955..85261123 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -34,7 +34,7 @@
get() = _isFingerprintAuthCurrentlyAllowed
private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
- override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+ override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>
get() = _isFaceAuthEnrolledAndEnabled
private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 21d0979..b403a7f 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -404,5 +404,9 @@
// Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
NOTE_CSD_LOWER_AUDIO = 1007;
+
+ // Notify the user about external display events related to screenshot.
+ // Package: com.android.systemui
+ NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f5562d2..4298c07 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -632,6 +632,10 @@
public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
@VirtualDeviceParams.DevicePolicy int devicePolicy) {
super.setDevicePolicy_enforcePermission();
+ if (!Flags.dynamicPolicy()) {
+ return;
+ }
+
switch (policyType) {
case POLICY_TYPE_RECENTS:
synchronized (mVirtualDeviceLock) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index ee41a69..65975e4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -1437,6 +1437,10 @@
if (!mDevCfgEnableContentProtectionReceiver) {
return false;
}
+ if (mDevCfgContentProtectionRequiredGroups.isEmpty()
+ && mDevCfgContentProtectionOptionalGroups.isEmpty()) {
+ return false;
+ }
}
return mContentProtectionConsentManager.isConsentGranted(userId)
&& mContentProtectionBlocklistManager.isAllowed(packageName);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 5fb889a..1650a96 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5309,7 +5309,7 @@
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
}
- long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE;
+ long flags = Context.BIND_AUTO_CREATE;
if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) {
flags |= Context.BIND_ALLOW_INSTANT;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0956c6d..5f1a7e7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3678,8 +3678,8 @@
|| (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0;
final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0;
- final boolean filterOutQuarantined =
- (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0;
+ final boolean matchQuarantined =
+ (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;
ProcessRecord attributedApp = null;
if (sdkSandboxClientAppUid > 0) {
@@ -3689,7 +3689,7 @@
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
isBindExternal, allowInstant, null /* fgsDelegateOptions */,
- inSharedIsolatedProcess, filterOutQuarantined);
+ inSharedIsolatedProcess, matchQuarantined);
if (res == null) {
return 0;
}
@@ -4202,7 +4202,7 @@
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,
callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,
allowInstant, fgsDelegateOptions, inSharedIsolatedProcess,
- false /* filterOutQuarantined */);
+ false /* matchQuarantined */);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -4211,7 +4211,7 @@
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
- boolean inSharedIsolatedProcess, boolean filterOutQuarantined) {
+ boolean inSharedIsolatedProcess, boolean matchQuarantined) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -4333,8 +4333,8 @@
if (allowInstant) {
flags |= PackageManager.MATCH_INSTANT;
}
- if (filterOutQuarantined) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
+ if (matchQuarantined) {
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
}
// TODO: come back and remove this assumption to triage all services
ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b43b986..31817f1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -58,7 +58,6 @@
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
@@ -14295,8 +14294,7 @@
private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
int callingUid, int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
- long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING
- | FILTER_OUT_QUARANTINED_COMPONENTS;
+ long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
List<ResolveInfo> receivers = null;
HashSet<ComponentName> singleUserReceivers = null;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2249607..0ab81a5 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -91,6 +91,7 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
+import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.StatsEvent;
@@ -99,8 +100,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.CpuScalingPolicyReader;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.RailStats;
import com.android.internal.os.RpmStats;
@@ -114,16 +117,21 @@
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.optimization.Flags;
+import com.android.server.power.stats.AggregatedPowerStatsConfig;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.BatteryUsageStatsStore;
+import com.android.server.power.stats.PowerStatsAggregator;
+import com.android.server.power.stats.PowerStatsScheduler;
+import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.wakeups.CpuWakeupStats;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -136,6 +144,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -153,20 +162,24 @@
static final String TAG = "BatteryStatsService";
static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason";
static final boolean DBG = false;
- private static final boolean BATTERY_USAGE_STORE_ENABLED = true;
private static IBatteryStats sService;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
+ private final MonotonicClock mMonotonicClock;
private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig;
final BatteryStatsImpl mStats;
final CpuWakeupStats mCpuWakeupStats;
- private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+ private final PowerStatsStore mPowerStatsStore;
+ private final PowerStatsAggregator mPowerStatsAggregator;
+ private final PowerStatsScheduler mPowerStatsScheduler;
private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
private final Context mContext;
private final BatteryExternalStatsWorker mWorker;
private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private final AtomicFile mConfigFile;
+
private volatile boolean mMonitorEnabled = true;
private native void getRailEnergyPowerStats(RailStats railStats);
@@ -376,6 +389,7 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));
mPowerProfile = new PowerProfile(context);
mCpuScalingPolicies = new CpuScalingPolicyReader().read();
@@ -391,23 +405,43 @@
.setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
.setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)
.build();
- mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this,
- this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies);
+ mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
+ systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
+ mCpuScalingPolicies);
mWorker = new BatteryExternalStatsWorker(context, mStats);
mStats.setExternalStatsSyncLocked(mWorker);
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
mStats.startTrackingSystemServerCpuTime();
- if (BATTERY_USAGE_STORE_ENABLED) {
- mBatteryUsageStatsStore =
- new BatteryUsageStatsStore(context, mStats, systemDir, mHandler);
- } else {
- mBatteryUsageStatsStore = null;
- }
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig();
+ mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
- mBatteryUsageStatsStore);
+ mPowerStatsStore);
+ mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
+ mStats.getHistory());
+ final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+ final long powerStatsAggregationPeriod = context.getResources().getInteger(
+ com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+ mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+ aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
+ Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);
mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
+ mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
+ }
+
+ private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ return config;
}
/**
@@ -466,9 +500,7 @@
*/
public void onSystemReady() {
mStats.onSystemReady();
- if (BATTERY_USAGE_STORE_ENABLED) {
- mBatteryUsageStatsStore.onSystemReady();
- }
+ mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());
}
private final class LocalService extends BatteryStatsInternal {
@@ -639,6 +671,9 @@
// Shutdown the thread we made.
mWorker.shutdown();
+
+ // To insure continuity, write the monotonic timeshift after writing the last history event
+ mMonotonicClock.write();
}
public static IBatteryStats getService() {
@@ -892,12 +927,8 @@
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
- if (!BATTERY_USAGE_STORE_ENABLED) {
- return StatsManager.PULL_SKIP;
- }
-
- final long sessionStart = mBatteryUsageStatsStore
- .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
+ final long sessionStart =
+ getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
synchronized (mStats) {
sessionEnd = mStats.getStartClockTime();
@@ -910,8 +941,7 @@
.aggregateSnapshots(sessionStart, sessionEnd)
.build();
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
- mBatteryUsageStatsStore
- .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
+ setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
@@ -2641,7 +2671,15 @@
}
private void dumpAggregatedStats(PrintWriter pw) {
- mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0);
+ mPowerStatsScheduler.aggregateAndDumpPowerStats(pw);
+ }
+
+ private void dumpPowerStatsStore(PrintWriter pw) {
+ mPowerStatsStore.dump(new IndentingPrintWriter(pw, " "));
+ }
+
+ private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) {
+ mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, " "));
}
private void dumpMeasuredEnergyStats(PrintWriter pw) {
@@ -2789,7 +2827,7 @@
synchronized (mStats) {
mStats.resetAllStatsAndHistoryLocked(
BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
- mBatteryUsageStatsStore.removeAllSnapshots();
+ mPowerStatsStore.reset();
pw.println("Battery stats and history reset.");
noOutput = true;
}
@@ -2891,6 +2929,12 @@
} else if ("--aggregated".equals(arg)) {
dumpAggregatedStats(pw);
return;
+ } else if ("--store".equals(arg)) {
+ dumpPowerStatsStore(pw);
+ return;
+ } else if ("--store-toc".equals(arg)) {
+ dumpPowerStatsStoreTableOfContents(pw);
+ return;
} else if ("-a".equals(arg)) {
flags |= BatteryStats.DUMP_VERBOSE;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2951,7 +2995,7 @@
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- mBatteryStatsConfig,
+ mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies);
@@ -2993,7 +3037,7 @@
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- mBatteryStatsConfig,
+ mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
null, mStats.mHandler, null, null,
mUserManagerUserInfoProvider, mPowerProfile,
mCpuScalingPolicies);
@@ -3360,6 +3404,52 @@
}
}
+ private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
+ "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
+
+ /**
+ * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
+ * in persistent file.
+ */
+ public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
+ synchronized (mConfigFile) {
+ Properties props = new Properties();
+ try (InputStream in = mConfigFile.openRead()) {
+ props.load(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+ }
+ props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
+ String.valueOf(timestamp));
+ FileOutputStream out = null;
+ try {
+ out = mConfigFile.startWrite();
+ props.store(out, "Statsd atom pull timestamps");
+ mConfigFile.finishWrite(out);
+ } catch (IOException e) {
+ mConfigFile.failWrite(out);
+ Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
+ * statsd atom pull.
+ */
+ public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
+ synchronized (mConfigFile) {
+ Properties props = new Properties();
+ try (InputStream in = mConfigFile.openRead()) {
+ props.load(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
+ }
+ return Long.parseLong(
+ props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
+ }
+ }
+
/**
* Sets battery AC charger to enabled/disabled, and freezes the battery state.
*/
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3771c05..f02b8c7 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -511,8 +511,8 @@
pw.print(prefix); pw.print("pid="); pw.println(mPid);
pw.print(prefix); pw.print("lastActivityTime=");
TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
- pw.print(prefix); pw.print("startUptimeTime=");
- TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw);
+ pw.print(prefix); pw.print("startUpTime=");
+ TimeUtils.formatDuration(mStartUptime, nowUptime, pw);
pw.print(prefix); pw.print("startElapsedTime=");
TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw);
pw.println();
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 8736a53..ac7d9c1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -221,9 +221,8 @@
/** Flags used when connecting to a sync adapter service */
private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS =
- Context.BindServiceFlags.of(
- Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT);
+ Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT);
/** Singleton instance. */
@GuardedBy("SyncManager.class")
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 098cb87..9f4f787 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -23,7 +23,7 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
import android.util.Slog;
@@ -201,20 +201,6 @@
* @param state The new display state.
* @param brightnessState The new display brightnessState.
* @param sdrBrightnessState The new display brightnessState for SDR layers.
- * @return A runnable containing work to be deferred until after we have
- * exited the critical section, or null if none.
- */
- public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState) {
- return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null);
- }
-
- /**
- * Sets the display state, if supported.
- *
- * @param state The new display state.
- * @param brightnessState The new display brightnessState.
- * @param sdrBrightnessState The new display brightnessState for SDR layers.
* @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.
* @return A runnable containing work to be deferred until after we have exited the critical
* section, or null if none.
@@ -223,7 +209,7 @@
int state,
float brightnessState,
float sdrBrightnessState,
- @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) {
+ @Nullable DisplayOffloadSession displayOffloadSession) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0689478..5b87eea 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -494,6 +494,7 @@
// If we would like to keep a particular eye on a package, we can set the package name.
private final boolean mExtraDisplayEventLogging;
+ private final String mExtraDisplayLoggingPackageName;
private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
@Override
@@ -584,8 +585,8 @@
mOverlayProperties = SurfaceControl.getOverlaySupport();
mSystemReady = false;
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
- final String name = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayEventLogging = !TextUtils.isEmpty(name);
+ mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
+ mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
}
public void setupSchedulerPolicies() {
@@ -757,7 +758,8 @@
mContext.registerReceiver(mIdleModeReceiver, filter);
- mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
+ mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
+ ? SmallAreaDetectionController.create(mContext) : null;
}
@VisibleForTesting
@@ -2934,12 +2936,17 @@
// Only send updates outside of DisplayManagerService for enabled displays
if (display.isEnabledLocked()) {
sendDisplayEventLocked(display, event);
+ } else if (mExtraDisplayEventLogging) {
+ Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
}
}
private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
int displayId = display.getDisplayIdLocked();
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+ if (mExtraDisplayEventLogging) {
+ Slog.i(TAG, "Deliver Display Event on Handler: " + event);
+ }
mHandler.sendMessage(msg);
}
@@ -3005,6 +3012,10 @@
// For cached apps, save the pending event until it becomes non-cached
synchronized (mPendingCallbackSelfLocked) {
PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+ if (extraLogging(callbackRecord.mPackageName)) {
+ Slog.i(TAG,
+ "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback);
+ }
if (pendingCallback == null) {
mPendingCallbackSelfLocked.put(uid,
new PendingCallback(callbackRecord, displayId, event));
@@ -3019,6 +3030,10 @@
mTempCallbacks.clear();
}
+ private boolean extraLogging(String packageName) {
+ return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+ }
+
// Runs on Handler thread.
// Delivers display group event notifications to callbacks.
private void deliverDisplayGroupEvent(int groupId, int event) {
@@ -3462,6 +3477,7 @@
public final int mUid;
private final IDisplayManagerCallback mCallback;
private @EventsMask AtomicLong mEventsMask;
+ private final String mPackageName;
public boolean mWifiDisplayScanRequested;
@@ -3471,6 +3487,9 @@
mUid = uid;
mCallback = callback;
mEventsMask = new AtomicLong(eventsMask);
+
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ mPackageName = packageNames == null ? null : packageNames[0];
}
public void updateEventsMask(@EventsMask long eventsMask) {
@@ -3479,7 +3498,8 @@
@Override
public void binderDied() {
- if (DEBUG) {
+ if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
+ mPackageName)) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
onCallbackDied(this);
@@ -3490,6 +3510,11 @@
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
+ if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
+ mPackageName)) {
+ Slog.i(TAG,
+ "Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 5e6ff46..d97c8e7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -30,7 +30,8 @@
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
- private static final String NOTIFICATION_TYPES = "on-hotplug-error";
+ private static final String NOTIFICATION_TYPES =
+ "on-hotplug-error, on-link-training-failure, on-cable-dp-incapable";
private final DisplayManagerService mService;
private final DisplayManagerFlags mFlags;
@@ -193,6 +194,12 @@
case "on-hotplug-error":
mService.getDisplayNotificationManager().onHotplugConnectionError();
break;
+ case "on-link-training-failure":
+ mService.getDisplayNotificationManager().onDisplayPortLinkTrainingFailure();
+ break;
+ case "on-cable-dp-incapable":
+ mService.getDisplayNotificationManager().onCableNotCapableDisplayPort();
+ break;
default:
getErrPrintWriter().println(
"Error: unexpected notification type=" + notificationType + ", use one of: "
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
index adaa539..bf384b0 100644
--- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.util.ArrayMap;
@@ -30,15 +31,14 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import java.io.PrintWriter;
-import java.util.Arrays;
import java.util.Map;
final class SmallAreaDetectionController {
- private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
- private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+ private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds);
+ private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold);
// TODO(b/281720315): Move this to DeviceConfig once server side ready.
private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
@@ -47,12 +47,8 @@
private final Object mLock = new Object();
private final Context mContext;
private final PackageManagerInternal mPackageManager;
- private final UserManagerInternal mUserManager;
@GuardedBy("mLock")
private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
- // TODO(b/298722189): Update allowlist when user changes
- @GuardedBy("mLock")
- private int[] mUserIds;
static SmallAreaDetectionController create(@NonNull Context context) {
final SmallAreaDetectionController controller =
@@ -67,7 +63,6 @@
SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
mContext = context;
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
- mUserManager = LocalServices.getService(UserManagerInternal.class);
deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(),
new SmallAreaDetectionController.OnPropertiesChangedListener());
@@ -76,6 +71,7 @@
@VisibleForTesting
void updateAllowlist(@Nullable String property) {
+ final Map<String, Float> allowPkgMap = new ArrayMap<>();
synchronized (mLock) {
mAllowPkgMap.clear();
if (property != null) {
@@ -86,8 +82,11 @@
.getStringArray(R.array.config_smallAreaDetectionAllowlist);
for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
}
- updateSmallAreaDetection();
+
+ if (mAllowPkgMap.isEmpty()) return;
+ allowPkgMap.putAll(mAllowPkgMap);
}
+ updateSmallAreaDetection(allowPkgMap);
}
@GuardedBy("mLock")
@@ -105,43 +104,32 @@
}
}
- @GuardedBy("mLock")
- private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
- for (int i = 0; i < mUserIds.length; i++) {
- final int userId = mUserIds[i];
- final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
- if (uid > 0) list.put(uid, threshold);
- }
- }
-
- @GuardedBy("mLock")
- private void updateSmallAreaDetection() {
- if (mAllowPkgMap.isEmpty()) return;
-
- mUserIds = mUserManager.getUserIds();
-
- final SparseArray<Float> uidThresholdList = new SparseArray<>();
- for (String pkg : mAllowPkgMap.keySet()) {
- final float threshold = mAllowPkgMap.get(pkg);
- updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+ private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) {
+ final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size());
+ for (String pkg : allowPkgMap.keySet()) {
+ final float threshold = allowPkgMap.get(pkg);
+ final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg);
+ if (stage != null) {
+ appIdThresholdList.put(stage.getAppId(), threshold);
+ }
}
- final int[] uids = new int[uidThresholdList.size()];
- final float[] thresholds = new float[uidThresholdList.size()];
- for (int i = 0; i < uidThresholdList.size(); i++) {
- uids[i] = uidThresholdList.keyAt(i);
- thresholds[i] = uidThresholdList.valueAt(i);
+ final int[] appIds = new int[appIdThresholdList.size()];
+ final float[] thresholds = new float[appIdThresholdList.size()];
+ for (int i = 0; i < appIdThresholdList.size(); i++) {
+ appIds[i] = appIdThresholdList.keyAt(i);
+ thresholds[i] = appIdThresholdList.valueAt(i);
}
- updateSmallAreaDetection(uids, thresholds);
+ updateSmallAreaDetection(appIds, thresholds);
}
@VisibleForTesting
- void updateSmallAreaDetection(int[] uids, float[] thresholds) {
- nativeUpdateSmallAreaDetection(uids, thresholds);
+ void updateSmallAreaDetection(int[] appIds, float[] thresholds) {
+ nativeUpdateSmallAreaDetection(appIds, thresholds);
}
- void setSmallAreaDetectionThreshold(int uid, float threshold) {
- nativeSetSmallAreaDetectionThreshold(uid, threshold);
+ void setSmallAreaDetectionThreshold(int appId, float threshold) {
+ nativeSetSmallAreaDetectionThreshold(appId, threshold);
}
void dump(PrintWriter pw) {
@@ -151,7 +139,6 @@
for (String pkg : mAllowPkgMap.keySet()) {
pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
}
- pw.println(" mUserIds=" + Arrays.toString(mUserIds));
}
}
@@ -167,11 +154,15 @@
private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
@Override
public void onPackageAdded(@NonNull String packageName, int uid) {
+ float threshold = 0.0f;
synchronized (mLock) {
if (mAllowPkgMap.containsKey(packageName)) {
- setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+ threshold = mAllowPkgMap.get(packageName);
}
}
+ if (threshold > 0.0f) {
+ setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d910e16..b002587 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -43,6 +43,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -395,7 +396,7 @@
@Override
public Runnable requestDisplayStateLocked(int state, float brightnessState,
- float sdrBrightnessState) {
+ float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {
if (state != mDisplayState) {
mDisplayState = state;
if (state == Display.STATE_OFF) {
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 fae8383..d953e8e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -71,6 +71,10 @@
Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
Flags::enablePowerThrottlingClamper);
+ private final FlagState mSmallAreaDetectionFlagState = new FlagState(
+ Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
+ Flags::enableSmallAreaDetection);
+
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
return mConnectedDisplayManagementFlagState.isEnabled();
@@ -147,6 +151,10 @@
return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
}
+ public boolean isSmallAreaDetectionEnabled() {
+ return mSmallAreaDetectionFlagState.isEnabled();
+ }
+
private static class FlagState {
private final String mName;
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9ab9c9d..9141814 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -104,3 +104,12 @@
bug: "211737588"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_small_area_detection"
+ namespace: "display_manager"
+ description: "Feature flag for SmallAreaDetection"
+ bug: "298722189"
+ is_fixed_read_only: true
+}
+
diff --git a/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
new file mode 100644
index 0000000..f683e81
--- /dev/null
+++ b/services/core/java/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetector.java
@@ -0,0 +1,132 @@
+/*
+ * 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.server.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.DisplayPortAltModeInfoListener;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
+
+/**
+ * Detects usb issues related to an external display connected.
+ */
+public class ConnectedDisplayUsbErrorsDetector implements DisplayPortAltModeInfoListener {
+ private static final String TAG = "ConnectedDisplayUsbErrorsDetector";
+
+ /**
+ * Dependency injection for {@link ConnectedDisplayUsbErrorsDetector}.
+ */
+ public interface Injector {
+
+ /**
+ * @return {@link UsbManager} service.
+ */
+ UsbManager getUsbManager();
+ }
+
+ /**
+ * USB errors listener
+ */
+ public interface Listener {
+
+ /**
+ * Link training failure callback.
+ */
+ void onDisplayPortLinkTrainingFailure();
+
+ /**
+ * DisplayPort capable device plugged-in, but cable is not supporting DisplayPort.
+ */
+ void onCableNotCapableDisplayPort();
+ }
+
+ private Listener mListener;
+ private final Injector mInjector;
+ private final Context mContext;
+ private final boolean mIsConnectedDisplayErrorHandlingEnabled;
+
+ ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+ @NonNull final Context context) {
+ this(flags, context, () -> context.getSystemService(UsbManager.class));
+ }
+
+ @VisibleForTesting
+ ConnectedDisplayUsbErrorsDetector(@NonNull final DisplayManagerFlags flags,
+ @NonNull final Context context, @NonNull final Injector injector) {
+ mContext = context;
+ mInjector = injector;
+ mIsConnectedDisplayErrorHandlingEnabled =
+ flags.isConnectedDisplayErrorHandlingEnabled();
+ }
+
+ /** Register listener for usb error events. */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ void registerListener(final Listener listener) {
+ if (!mIsConnectedDisplayErrorHandlingEnabled) {
+ return;
+ }
+
+ final var usbManager = mInjector.getUsbManager();
+ if (usbManager == null) {
+ Slog.e(TAG, "UsbManager is null");
+ return;
+ }
+
+ mListener = listener;
+
+ try {
+ usbManager.registerDisplayPortAltModeInfoListener(mContext.getMainExecutor(), this);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Failed to register listener", e);
+ }
+ }
+
+ /**
+ * Callback upon changes in {@link DisplayPortAltModeInfo}.
+ * @param portId String describing the {@link android.hardware.usb.UsbPort} that was changed.
+ * @param info New {@link DisplayPortAltModeInfo} for the corresponding portId.
+ */
+ @Override
+ public void onDisplayPortAltModeInfoChanged(@NonNull String portId,
+ @NonNull DisplayPortAltModeInfo info) {
+ if (mListener == null) {
+ return;
+ }
+
+ if (DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED == info.getPartnerSinkStatus()
+ && DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE == info.getCableStatus()
+ ) {
+ mListener.onCableNotCapableDisplayPort();
+ return;
+ }
+
+ if (LINK_TRAINING_STATUS_FAILURE == info.getLinkTrainingStatus()) {
+ mListener.onDisplayPortLinkTrainingFailure();
+ return;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
index 7d83e90..5cdef38 100644
--- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -34,12 +34,16 @@
/**
* Manages notifications for {@link com.android.server.display.DisplayManagerService}.
*/
-public class DisplayNotificationManager {
+public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDetector.Listener {
/** Dependency injection interface for {@link DisplayNotificationManager} */
public interface Injector {
/** Get {@link NotificationManager} service or null if not available. */
@Nullable
NotificationManager getNotificationManager();
+
+ /** Get {@link ConnectedDisplayUsbErrorsDetector} or null if not available. */
+ @Nullable
+ ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector();
}
private static final String TAG = "DisplayNotificationManager";
@@ -52,9 +56,22 @@
private final Context mContext;
private final boolean mConnectedDisplayErrorHandlingEnabled;
private NotificationManager mNotificationManager;
+ private ConnectedDisplayUsbErrorsDetector mConnectedDisplayUsbErrorsDetector;
public DisplayNotificationManager(final DisplayManagerFlags flags, final Context context) {
- this(flags, context, () -> context.getSystemService(NotificationManager.class));
+ this(flags, context, new Injector() {
+ @Nullable
+ @Override
+ public NotificationManager getNotificationManager() {
+ return context.getSystemService(NotificationManager.class);
+ }
+
+ @Nullable
+ @Override
+ public ConnectedDisplayUsbErrorsDetector getUsbErrorsDetector() {
+ return new ConnectedDisplayUsbErrorsDetector(flags, context);
+ }
+ });
}
@VisibleForTesting
@@ -75,6 +92,44 @@
Slog.e(TAG, "onBootCompleted: NotificationManager is null");
return;
}
+
+ mConnectedDisplayUsbErrorsDetector = mInjector.getUsbErrorsDetector();
+ if (mConnectedDisplayUsbErrorsDetector != null) {
+ mConnectedDisplayUsbErrorsDetector.registerListener(this);
+ }
+ }
+
+ /**
+ * Display error notification upon DisplayPort link training failure.
+ */
+ @Override
+ public void onDisplayPortLinkTrainingFailure() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onDisplayPortLinkTrainingFailure:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_unavailable_notification_title,
+ R.string.connected_display_unavailable_notification_content));
+ }
+
+ /**
+ * Display error notification upon cable not capable of DisplayPort connected to a device
+ * capable of DisplayPort.
+ */
+ @Override
+ public void onCableNotCapableDisplayPort() {
+ if (!mConnectedDisplayErrorHandlingEnabled) {
+ Slog.d(TAG, "onCableNotCapableDisplayPort:"
+ + " mConnectedDisplayErrorHandlingEnabled is false");
+ return;
+ }
+
+ sendErrorNotification(createErrorNotification(
+ R.string.connected_display_cable_dont_support_displays_notification_title,
+ R.string.connected_display_cable_dont_support_displays_notification_content));
}
/**
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index eb997ba..8bc69c2 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -345,7 +345,7 @@
}
private void notifyBluetoothRoutesUpdated() {
- mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated();
}
private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 93f6ff3..33190ad 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -231,7 +231,7 @@
}
if (isDeviceRouteChanged) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
}
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index ddeeacc..2b01001 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -136,12 +136,8 @@
*/
interface BluetoothRoutesUpdatedListener {
- /**
- * Called when Bluetooth routes have changed.
- *
- * @param routes updated Bluetooth routes list.
- */
- void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+ /** Called when Bluetooth routes have changed. */
+ void onBluetoothRoutesUpdated();
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index e17f4a3..7876095 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -93,12 +93,8 @@
*/
interface OnDeviceRouteChangedListener {
- /**
- * Called when device route has changed.
- *
- * @param deviceRoute non-null device route.
- */
- void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
+ /** Called when device route has changed. */
+ void onDeviceRouteChanged();
}
}
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index 1980403..ba3cecf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -280,7 +280,7 @@
private void notifyBluetoothRoutesUpdated() {
if (mListener != null) {
- mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes());
+ mListener.onBluetoothRoutesUpdated();
}
}
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 971d11f..6ba40ae 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -165,8 +165,8 @@
}
}
- private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ private void notifyDeviceRouteUpdate() {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
@@ -177,7 +177,7 @@
synchronized (LegacyDeviceRouteController.this) {
mDeviceRoute = deviceRoute;
}
- notifyDeviceRouteUpdate(deviceRoute);
+ notifyDeviceRouteUpdate();
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6c9aa4b..67a1ccd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -109,21 +109,28 @@
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
+ mBluetoothRouteController =
+ BluetoothRouteController.createInstance(
+ context,
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
- mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> {
- mHandler.post(() -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
- });
+ mDeviceRouteController =
+ DeviceRouteController.createInstance(
+ context,
+ () -> {
+ mHandler.post(
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ });
mAudioManager.addOnDevicesForAttributesChangedListener(
AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
new file mode 100644
index 0000000..ff70cb3
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.server.media.projection;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+
+public class MediaProjectionSessionIdGenerator {
+
+ private static final String PREFERENCES_FILE_NAME = "media_projection_session_id";
+ private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key";
+ private static final int SESSION_ID_DEFAULT_VALUE = 0;
+
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static MediaProjectionSessionIdGenerator sInstance;
+
+ private final Object mSessionIdLock = new Object();
+
+ @GuardedBy("mSessionIdLock")
+ private final SharedPreferences mSharedPreferences;
+
+ /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */
+ public static MediaProjectionSessionIdGenerator getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ File preferencesFile =
+ new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+ SharedPreferences preferences =
+ context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+ sInstance = new MediaProjectionSessionIdGenerator(preferences);
+ }
+ return sInstance;
+ }
+ }
+
+ @VisibleForTesting
+ public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) {
+ this.mSharedPreferences = sharedPreferences;
+ }
+
+ /** Returns the current session ID. This value is persisted across reboots. */
+ public int getCurrentSessionId() {
+ synchronized (mSessionIdLock) {
+ return getCurrentSessionIdInternal();
+ }
+ }
+
+ /**
+ * Creates and returns a new session ID. This value will be persisted as the new current session
+ * ID, and will be persisted across reboots.
+ */
+ public int createAndGetNewSessionId() {
+ synchronized (mSessionIdLock) {
+ int newSessionId = getCurrentSessionId() + 1;
+ setSessionIdInternal(newSessionId);
+ return newSessionId;
+ }
+ }
+
+ @GuardedBy("mSessionIdLock")
+ private void setSessionIdInternal(int value) {
+ mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply();
+ }
+
+ @GuardedBy("mSessionIdLock")
+ private int getCurrentSessionIdInternal() {
+ return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 7db7bf5..30017be 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -505,6 +505,10 @@
int filterCallingUid, int userId, boolean resolveForStart,
boolean allowDynamicSplits) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
+
+ // Allow to match activities of quarantined packages.
+ flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;
+
final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
@@ -647,11 +651,6 @@
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
- // Only if the query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
@@ -4047,9 +4046,6 @@
flags = updateFlagsForComponent(flags, userId);
enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "get provider info");
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
ParsedProvider p = mComponentResolver.getProvider(component);
if (DEBUG_PACKAGE_INFO) Log.v(
TAG, "getProviderInfo " + component + ": " + p);
@@ -4679,9 +4675,6 @@
int callingUid) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId);
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,
userId);
boolean checkedGrants = false;
@@ -4794,13 +4787,6 @@
false /* checkShell */, "queryContentProviders");
if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForComponent(flags, userId);
-
- // Only if the service query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
-
ArrayList<ProviderInfo> finalList = null;
final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName,
metaDataKey, uid, flags, userId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index edb45aa..2967818 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1269,10 +1269,9 @@
replace = true;
if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);
}
- final AndroidPackage oldPackage = mPm.mPackages.get(pkgName);
- if (replace && oldPackage != null) {
+ if (replace) {
// Prevent apps opting out from runtime permissions
- final int oldTargetSdk = oldPackage.getTargetSdkVersion();
+ final int oldTargetSdk = ps.getTargetSdkVersion();
final int newTargetSdk = parsedPackage.getTargetSdkVersion();
if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1
&& newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -1284,10 +1283,10 @@
+ " target SDK " + oldTargetSdk + " does.");
}
// Prevent persistent apps from being updated
- if (oldPackage.isPersistent()
+ if (ps.isPersistent()
&& ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK,
- "Package " + oldPackage.getPackageName() + " is a persistent app. "
+ "Package " + pkgName + " is a persistent app. "
+ "Persistent apps are not updateable.");
}
}
@@ -1668,7 +1667,7 @@
}
// don't allow a system upgrade unless the upgrade hash matches
- if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null
+ if (oldPackageState.getRestrictUpdateHash() != null
&& oldPackageState.isSystem()) {
final byte[] digestBytes;
try {
@@ -1684,12 +1683,13 @@
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"Could not compute hash: " + pkgName11);
}
- if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) {
+ if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) {
throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,
"New package fails restrict-update check: " + pkgName11);
}
// retain upgrade restriction
- parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash());
+ parsedPackage.setRestrictUpdateHash(
+ oldPackageState.getRestrictUpdateHash());
}
if (oldPackage != null) {
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 148e0df..9ad8318 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -89,6 +89,21 @@
if (packageState == null || packageState.getPkg() == null) {
throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
}
+ final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
+ mPm.mUserManager.getUserIds(), true);
+ final UserHandle userForMove;
+ if (installedUserIds.length > 0) {
+ userForMove = UserHandle.of(installedUserIds[0]);
+ } else {
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST,
+ "Package is not installed for any user");
+ }
+ for (int userId : installedUserIds) {
+ if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid,
+ userId)) {
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
+ }
+ }
final AndroidPackage pkg = packageState.getPkg();
if (packageState.isSystem()) {
throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
@@ -134,8 +149,6 @@
final String label = String.valueOf(pm.getApplicationLabel(
AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
final int targetSdkVersion = pkg.getTargetSdkVersion();
- final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
- mPm.mUserManager.getUserIds(), true);
final String fromCodePath;
if (codeFile.getParentFile().getName().startsWith(
PackageManagerService.RANDOM_DIR_PREFIX)) {
@@ -303,8 +316,8 @@
final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
final InstallingSession installingSession = new InstallingSession(origin, move,
installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
- volumeUuid, user, packageAbiOverride, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
- lite, mPm);
+ volumeUuid, userForMove, packageAbiOverride,
+ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
installingSession.movePackage();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68aa93d..abeabc9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1469,8 +1469,7 @@
archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
archPkg.versionCode = (int) longVersionCode;
- // TODO(b/297916136): extract target sdk version.
- archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+ archPkg.targetSdkVersion = ps.getTargetSdkVersion();
// These get translated in flags important for user data management.
archPkg.defaultToDeviceProtectedStorage = String.valueOf(
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2e60064..9f4e86d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -99,6 +99,7 @@
private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
private static final int UPDATE_AVAILABLE = 1 << 2;
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
+ private static final int PERSISTENT = 1 << 4;
}
private int mBooleans;
@@ -217,6 +218,11 @@
@Nullable
private String mAppMetadataFilePath;
+ private int mTargetSdkVersion;
+
+ @Nullable
+ private byte[] mRestrictUpdateHash;
+
/**
* Snapshot support.
*/
@@ -535,6 +541,24 @@
onChanged();
}
+ public PackageSetting setIsPersistent(boolean isPersistent) {
+ setBoolean(Booleans.PERSISTENT, isPersistent);
+ onChanged();
+ return this;
+ }
+
+ public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
+ mTargetSdkVersion = targetSdkVersion;
+ onChanged();
+ return this;
+ }
+
+ public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) {
+ mRestrictUpdateHash = restrictUpdateHash;
+ onChanged();
+ return this;
+ }
+
@Override
public int getSharedUserAppId() {
return mSharedUserAppId;
@@ -701,6 +725,9 @@
categoryOverride = other.categoryOverride;
mDomainSetId = other.mDomainSetId;
mAppMetadataFilePath = other.mAppMetadataFilePath;
+ mTargetSdkVersion = other.mTargetSdkVersion;
+ mRestrictUpdateHash = other.mRestrictUpdateHash == null
+ ? null : other.mRestrictUpdateHash.clone();
usesSdkLibraries = other.usesSdkLibraries != null
? Arrays.copyOf(other.usesSdkLibraries,
@@ -1572,6 +1599,11 @@
return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
}
+ @Override
+ public boolean isPersistent() {
+ return getBoolean(Booleans.PERSISTENT);
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1714,11 +1746,21 @@
return mAppMetadataFilePath;
}
+ @DataClass.Generated.Member
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable byte[] getRestrictUpdateHash() {
+ return mRestrictUpdateHash;
+ }
+
@DataClass.Generated(
- time = 1694196905013L,
+ time = 1696979728639L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index da14397..203e1de 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -517,12 +517,6 @@
if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
- // Only if the service query is coming from the system process,
- // it should be allowed to match quarantined components
- if (callingUid != Process.SYSTEM_UID) {
- flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
- }
-
final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);
flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0cac790..8d8acfd4 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,8 @@
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
- newDomainSetId);
+ newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
} else {
// make a deep copy to avoid modifying any existing system state.
pkgSetting = new PackageSetting(pkgSetting);
@@ -240,7 +241,8 @@
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups(), newDomainSetId);
+ parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
}
if (createNewPackage && originalPkgSetting != null) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6e3b538..397a841 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1060,7 +1060,8 @@
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -1080,7 +1081,10 @@
.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
- .setDomainSetId(domainSetId);
+ .setDomainSetId(domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setFlags(pkgFlags)
.setPrivateFlags(pkgPrivateFlags);
} else {
@@ -1092,7 +1096,10 @@
null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
usesStaticLibraries, usesStaticLibrariesVersions,
- createMimeGroups(mimeGroupNames), domainSetId);
+ createMimeGroups(mimeGroupNames), domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setLastModifiedTime(codePath.lastModified());
if (sharedUser != null) {
pkgSetting.setSharedUserAppId(sharedUser.mAppId);
@@ -1206,7 +1213,8 @@
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ int targetSdkVersion, byte[] restrictUpdatedHash)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
if (sharedUser != null) {
@@ -1258,7 +1266,10 @@
pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)
.setSecondaryCpuAbi(secondaryCpuAbi)
.updateMimeGroups(mimeGroupNames)
- .setDomainSetId(domainSetId);
+ .setDomainSetId(domainSetId)
+ .setIsPersistent(isPersistent)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
if (usesSdkLibraries != null && usesSdkLibrariesVersions != null
&& usesSdkLibraries.length == usesSdkLibrariesVersions.length) {
@@ -6400,17 +6411,26 @@
}
boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) {
+ ArrayList<PersistentPreferredActivity> removed = null;
PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);
Iterator<PersistentPreferredActivity> it = ppir.filterIterator();
boolean changed = false;
while (it.hasNext()) {
PersistentPreferredActivity ppa = it.next();
if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) {
- ppir.removeFilter(ppa);
- changed = true;
- break;
+ if (removed == null) {
+ removed = new ArrayList<>();
+ }
+ removed.add(ppa);
}
}
+ if (removed != null) {
+ for (int i = 0; i < removed.size(); i++) {
+ PersistentPreferredActivity ppa = removed.get(i);
+ ppir.removeFilter(ppa);
+ }
+ changed = true;
+ }
if (changed) {
onChanged();
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0e98158..7331bc1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1335,18 +1335,6 @@
&& user.profileGroupId == profile.profileGroupId);
}
- private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode,
- boolean useManagedActions) {
- Intent intent = new Intent();
- intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions));
- intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
- intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle());
- intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier());
- intent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- return intent;
- }
-
private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {
return useManagedActions ?
enableQuietMode ?
@@ -1359,12 +1347,20 @@
private void broadcastProfileAvailabilityChanges(UserInfo profileInfo,
UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) {
- Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode,
- useManagedActions);
+ Intent availabilityIntent = new Intent();
+ availabilityIntent.setAction(
+ getAvailabilityIntentAction(enableQuietMode, useManagedActions));
+ availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode);
+ availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle());
+ availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE,
+ profileInfo.getUserHandle().getIdentifier());
if (profileInfo.isManagedProfile()) {
getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
availabilityIntent, parentHandle, /* requiresPermission= */ true);
}
+ availabilityIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+
// TODO(b/302708423): Restrict the apps that can receive these intents in case of a private
// profile.
final Bundle options = new BroadcastOptions()
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d804e01..61e96ca 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -379,7 +379,7 @@
ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
| flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
| flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
- if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0
+ if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0
&& state.isQuarantined()) {
ai.enabled = false;
} else if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3f347e4..e7137bb 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -434,4 +434,26 @@
*/
@Nullable
String getApexModuleName();
+
+ /**
+ * @see ApplicationInfo#FLAG_PERSISTENT
+ * @see R.styleable#AndroidManifestApplication_persistent
+ * @hide
+ */
+ boolean isPersistent();
+
+ /**
+ * @see ApplicationInfo#targetSdkVersion
+ * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
+ * @hide
+ */
+ int getTargetSdkVersion();
+
+ /**
+ * @see R.styleable#AndroidManifestRestrictUpdate
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ byte[] getRestrictUpdateHash();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index 7b07e5b..cd3583b 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -16,9 +16,9 @@
package com.android.server.pm.pkg;
-import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS;
import android.annotation.NonNull;
import android.content.pm.ComponentInfo;
@@ -147,7 +147,7 @@
return true;
}
- if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) {
+ if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) {
return false;
}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index 6cc9d0a..bc90f5c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -18,15 +18,26 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.os.BatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
-import java.io.PrintWriter;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -34,25 +45,75 @@
* etc) covering a specific period of power usage history.
*/
class AggregatedPowerStats {
+ private static final String TAG = "AggregatedPowerStats";
+ private static final int MAX_CLOCK_UPDATES = 100;
+ private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats";
+
private final PowerComponentAggregatedPowerStats[] mPowerComponentStats;
- @CurrentTimeMillisLong
- private long mStartTime;
+ static class ClockUpdate {
+ public long monotonicTime;
+ @CurrentTimeMillisLong public long currentTime;
+ }
+
+ private final List<ClockUpdate> mClockUpdates = new ArrayList<>();
@DurationMillisLong
private long mDurationMs;
- AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) {
- this.mPowerComponentStats = powerComponentAggregatedPowerStats;
+ AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ List<AggregatedPowerStatsConfig.PowerComponent> configs =
+ aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
+ mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()];
+ for (int i = 0; i < configs.size(); i++) {
+ mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i));
+ }
}
- void setStartTime(@CurrentTimeMillisLong long startTime) {
- mStartTime = startTime;
+ private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats(
+ AggregatedPowerStatsConfig.PowerComponent config) {
+ switch (config.getPowerComponentId()) {
+ case BatteryConsumer.POWER_COMPONENT_CPU:
+ return new CpuAggregatedPowerStats(config);
+ default:
+ return new PowerComponentAggregatedPowerStats(config);
+ }
}
- @CurrentTimeMillisLong
- public long getStartTime() {
- return mStartTime;
+ /**
+ * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change,
+ * there may be multiple clock updates in one set of aggregated stats.
+ *
+ * @param monotonicTime monotonic time in milliseconds, see
+ * {@link com.android.internal.os.MonotonicClock}
+ * @param currentTime current time in milliseconds, see {@link System#currentTimeMillis()}
+ */
+ void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) {
+ ClockUpdate clockUpdate = new ClockUpdate();
+ clockUpdate.monotonicTime = monotonicTime;
+ clockUpdate.currentTime = currentTime;
+ if (mClockUpdates.size() < MAX_CLOCK_UPDATES) {
+ mClockUpdates.add(clockUpdate);
+ } else {
+ Slog.i(TAG, "Too many clock updates. Replacing the previous update with "
+ + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime));
+ mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate);
+ }
+ }
+
+ /**
+ * Start time according to {@link com.android.internal.os.MonotonicClock}
+ */
+ long getStartTime() {
+ if (mClockUpdates.isEmpty()) {
+ return 0;
+ } else {
+ return mClockUpdates.get(0).monotonicTime;
+ }
+ }
+
+ List<ClockUpdate> getClockUpdates() {
+ return mClockUpdates;
}
void setDuration(long durationMs) {
@@ -73,13 +134,14 @@
return null;
}
- void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state,
+ long time) {
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.setState(stateId, state, time);
}
}
- void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long time) {
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.setUidState(uid, stateId, state, time);
@@ -106,20 +168,90 @@
}
void reset() {
- mStartTime = 0;
+ mClockUpdates.clear();
mDurationMs = 0;
for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
stats.reset();
}
}
- void dump(PrintWriter pw) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.print("Start time: ");
- ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime));
- ipw.print(" duration: ");
- ipw.print(mDurationMs);
- ipw.println();
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+ for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {
+ stats.writeXml(serializer);
+ }
+ serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS);
+ serializer.flush();
+ }
+
+ @NonNull
+ public static AggregatedPowerStats createFromXml(
+ TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig)
+ throws XmlPullParserException, IOException {
+ AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
+ boolean inElement = false;
+ boolean skipToEnd = false;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) {
+ if (!skipToEnd && eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case XML_TAG_AGGREGATED_POWER_STATS:
+ inElement = true;
+ break;
+ case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT:
+ if (!inElement) {
+ break;
+ }
+
+ int powerComponentId = parser.getAttributeInt(null,
+ PowerComponentAggregatedPowerStats.XML_ATTR_ID);
+ for (PowerComponentAggregatedPowerStats powerComponent :
+ stats.mPowerComponentStats) {
+ if (powerComponent.powerComponentId == powerComponentId) {
+ if (!powerComponent.readFromXml(parser)) {
+ skipToEnd = true;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return stats;
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ StringBuilder sb = new StringBuilder();
+ long baseTime = 0;
+ for (int i = 0; i < mClockUpdates.size(); i++) {
+ ClockUpdate clockUpdate = mClockUpdates.get(i);
+ sb.setLength(0);
+ if (i == 0) {
+ baseTime = clockUpdate.monotonicTime;
+ sb.append("Start time: ")
+ .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime))
+ .append(" (")
+ .append(baseTime)
+ .append(") duration: ")
+ .append(mDurationMs);
+ ipw.println(sb);
+ } else {
+ sb.setLength(0);
+ sb.append("Clock update: ");
+ TimeUtils.formatDuration(
+ clockUpdate.monotonicTime - baseTime, sb,
+ TimeUtils.HUNDRED_DAY_FIELD_LEN + 3);
+ sb.append(" ").append(
+ DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime));
+ ipw.increaseIndent();
+ ipw.println(sb);
+ ipw.decreaseIndent();
+ }
+ }
ipw.println("Device");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
new file mode 100644
index 0000000..477c228
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -0,0 +1,156 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.IntDef;
+import android.os.BatteryConsumer;
+
+import com.android.internal.os.MultiStateStats;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Configuration that controls how power stats are aggregated. It determines which state changes
+ * are to be considered as essential dimensions ("tracked states") for each power component (CPU,
+ * WiFi, etc). Also, it determines which states are tracked globally and which ones on a per-UID
+ * basis.
+ */
+public class AggregatedPowerStatsConfig {
+ public static final int STATE_POWER = 0;
+ public static final int STATE_SCREEN = 1;
+ public static final int STATE_PROCESS_STATE = 2;
+
+ @IntDef({
+ STATE_POWER,
+ STATE_SCREEN,
+ STATE_PROCESS_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TrackedState {
+ }
+
+ static final String STATE_NAME_POWER = "pwr";
+ static final int POWER_STATE_BATTERY = 0;
+ static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
+ static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
+
+ static final String STATE_NAME_SCREEN = "scr";
+ static final int SCREEN_STATE_ON = 0;
+ static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
+ static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
+
+ static final String STATE_NAME_PROCESS_STATE = "ps";
+ static final String[] STATE_LABELS_PROCESS_STATE;
+
+ static {
+ String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
+ procStateLabels[i] = BatteryConsumer.processStateToString(i);
+ }
+ STATE_LABELS_PROCESS_STATE = procStateLabels;
+ }
+
+ /**
+ * Configuration for a give power component (CPU, WiFi, etc)
+ */
+ public static class PowerComponent {
+ private final int mPowerComponentId;
+ private @TrackedState int[] mTrackedDeviceStates;
+ private @TrackedState int[] mTrackedUidStates;
+
+ PowerComponent(int powerComponentId) {
+ this.mPowerComponentId = powerComponentId;
+ }
+
+ /**
+ * Configures which states should be tracked as separate dimensions for the entire device.
+ */
+ public PowerComponent trackDeviceStates(@TrackedState int... states) {
+ mTrackedDeviceStates = states;
+ return this;
+ }
+
+ /**
+ * Configures which states should be tracked as separate dimensions on a per-UID basis.
+ */
+ public PowerComponent trackUidStates(@TrackedState int... states) {
+ mTrackedUidStates = states;
+ return this;
+ }
+
+ public int getPowerComponentId() {
+ return mPowerComponentId;
+ }
+
+ public MultiStateStats.States[] getDeviceStateConfig() {
+ return new MultiStateStats.States[]{
+ new MultiStateStats.States(STATE_NAME_POWER,
+ isTracked(mTrackedDeviceStates, STATE_POWER),
+ STATE_LABELS_POWER),
+ new MultiStateStats.States(STATE_NAME_SCREEN,
+ isTracked(mTrackedDeviceStates, STATE_SCREEN),
+ STATE_LABELS_SCREEN),
+ };
+ }
+
+ public MultiStateStats.States[] getUidStateConfig() {
+ return new MultiStateStats.States[]{
+ new MultiStateStats.States(STATE_NAME_POWER,
+ isTracked(mTrackedUidStates, STATE_POWER),
+ AggregatedPowerStatsConfig.STATE_LABELS_POWER),
+ new MultiStateStats.States(STATE_NAME_SCREEN,
+ isTracked(mTrackedUidStates, STATE_SCREEN),
+ AggregatedPowerStatsConfig.STATE_LABELS_SCREEN),
+ new MultiStateStats.States(STATE_NAME_PROCESS_STATE,
+ isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
+ AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE),
+ };
+ }
+
+ private boolean isTracked(int[] trackedStates, int state) {
+ if (trackedStates == null) {
+ return false;
+ }
+
+ for (int trackedState : trackedStates) {
+ if (trackedState == state) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final List<PowerComponent> mPowerComponents = new ArrayList<>();
+
+ /**
+ * Creates a configuration for the specified power component, which may be one of the
+ * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
+ * a custom power component.
+ */
+ public PowerComponent trackPowerComponent(int powerComponentId) {
+ PowerComponent builder = new PowerComponent(powerComponentId);
+ mPowerComponents.add(builder);
+ return builder;
+ }
+
+ public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
+ return mPowerComponents;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
new file mode 100644
index 0000000..7ba4330
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.power.stats;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "aggregated-power-stats";
+
+ private final AggregatedPowerStats mAggregatedPowerStats;
+
+ AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
+ super(TYPE);
+ mAggregatedPowerStats = aggregatedPowerStats;
+ }
+
+ public AggregatedPowerStats getAggregatedPowerStats() {
+ return mAggregatedPowerStats;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ mAggregatedPowerStats.writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mAggregatedPowerStats.dump(ipw);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 613f189..a9c2bc2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -124,6 +124,7 @@
import com.android.internal.os.KernelSingleUidTimeReader;
import com.android.internal.os.LongArrayMultiStateCounter;
import com.android.internal.os.LongMultiStateCounter;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.internal.os.RailStats;
@@ -283,7 +284,6 @@
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
- private final PowerStatsAggregator mPowerStatsAggregator;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -356,6 +356,11 @@
protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
@NonNull
+ public BatteryStatsHistory getHistory() {
+ return mHistory;
+ }
+
+ @NonNull
BatteryStatsHistory copyHistory() {
return mHistory.copy();
}
@@ -1718,14 +1723,7 @@
return mMaxLearnedBatteryCapacityUah;
}
- public BatteryStatsImpl() {
- this(Clock.SYSTEM_CLOCK);
- }
-
- public BatteryStatsImpl(Clock clock) {
- this(clock, null);
- }
-
+ @VisibleForTesting
public BatteryStatsImpl(Clock clock, File historyDirectory) {
init(clock);
mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
@@ -1737,18 +1735,19 @@
mCheckinFile = null;
mStatsFile = null;
mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+ new MonotonicClock(0, mClock));
} else {
mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock,
+ new MonotonicClock(0, mClock));
}
mPlatformIdleStateCallback = null;
mEnergyConsumerRetriever = null;
mUserInfoProvider = null;
mCpuPowerStatsCollector = null;
- mPowerStatsAggregator = null;
}
private void init(Clock clock) {
@@ -10906,20 +10905,12 @@
return mTmpCpuTimeInFreq;
}
- public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir,
+ public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
+ @NonNull MonotonicClock monotonicClock, @Nullable File systemDir,
@NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,
@Nullable EnergyStatsRetriever energyStatsCb,
@NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
@NonNull CpuScalingPolicies cpuScalingPolicies) {
- this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider,
- powerProfile, cpuScalingPolicies);
- }
-
- private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock,
- @Nullable File systemDir, @NonNull Handler handler,
- @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb,
- @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,
- @NonNull CpuScalingPolicies cpuScalingPolicies) {
init(clock);
mBatteryStatsConfig = config;
@@ -10936,31 +10927,19 @@
mCheckinFile = null;
mDailyFile = null;
mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
- mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);
}
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,
mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
- PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
- builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
- .trackDeviceStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN)
- .trackUidStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN,
- PowerStatsAggregator.STATE_PROCESS_STATE);
-
- mPowerStatsAggregator = builder.build();
-
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -15702,20 +15681,23 @@
}
/**
+ * Schedules an immediate (but asynchronous) collection of PowerStats samples.
+ * Callers will need to wait for the collection to complete on the handler thread.
+ */
+ public void schedulePowerStatsSampleCollection() {
+ if (mCpuPowerStatsCollector == null) {
+ return;
+ }
+ mCpuPowerStatsCollector.forceSchedule();
+ }
+
+ /**
* Grabs one sample of PowerStats and prints it.
*/
public void dumpStatsSample(PrintWriter pw) {
mCpuPowerStatsCollector.collectAndDump(pw);
}
- /**
- * Aggregates power stats between the specified times and prints them.
- */
- public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) {
- mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs,
- stats-> stats.dump(pw));
- }
-
private final Runnable mWriteAsyncRunnable = () -> {
synchronized (BatteryStatsImpl.this) {
writeSyncLocked();
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index f6fa9f2..851a3f7 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -46,7 +46,7 @@
private static final String TAG = "BatteryUsageStatsProv";
private final Context mContext;
private final BatteryStats mStats;
- private final BatteryUsageStatsStore mBatteryUsageStatsStore;
+ private final PowerStatsStore mPowerStatsStore;
private final PowerProfile mPowerProfile;
private final CpuScalingPolicies mCpuScalingPolicies;
private final Object mLock = new Object();
@@ -58,10 +58,10 @@
@VisibleForTesting
public BatteryUsageStatsProvider(Context context, BatteryStats stats,
- BatteryUsageStatsStore batteryUsageStatsStore) {
+ PowerStatsStore powerStatsStore) {
mContext = context;
mStats = stats;
- mBatteryUsageStatsStore = batteryUsageStatsStore;
+ mPowerStatsStore = powerStatsStore;
mPowerProfile = stats instanceof BatteryStatsImpl
? ((BatteryStatsImpl) stats).getPowerProfile()
: new PowerProfile(context);
@@ -314,20 +314,52 @@
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
customEnergyConsumerNames, includePowerModels, includeProcessStateData,
minConsumedPowerThreshold);
- if (mBatteryUsageStatsStore == null) {
- Log.e(TAG, "BatteryUsageStatsStore is unavailable");
+ if (mPowerStatsStore == null) {
+ Log.e(TAG, "PowerStatsStore is unavailable");
return builder.build();
}
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- for (long timestamp : timestamps) {
- if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) {
- final BatteryUsageStats snapshot =
- mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
- if (snapshot == null) {
- continue;
- }
+ List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+ for (PowerStatsSpan.Metadata spanMetadata : toc) {
+ if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) {
+ continue;
+ }
+ // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the
+ // session end time.
+ //
+ // The following algorithm is correct when there is only one time frame in the span.
+ // When the wall-clock time is adjusted in the middle of an stats span,
+ // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm
+ // only covers some situations, but not others. When using the resulting data for
+ // analysis, we should always pay attention to the full set of included timeframes.
+ // TODO(b/298459065): switch to monotonic clock
+ long minTime = Long.MAX_VALUE;
+ long maxTime = 0;
+ for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) {
+ long spanEndTime = timeFrame.startTime + timeFrame.duration;
+ minTime = Math.min(minTime, spanEndTime);
+ maxTime = Math.max(maxTime, spanEndTime);
+ }
+
+ // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*,
+ // while the "to" timestamp is *inclusive*.
+ boolean isInRange =
+ (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
+ && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+ if (!isInRange) {
+ continue;
+ }
+
+ PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan(
+ spanMetadata.getId(), BatteryUsageStatsSection.TYPE);
+ if (powerStatsSpan == null) {
+ continue;
+ }
+
+ for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) {
+ BatteryUsageStats snapshot =
+ ((BatteryUsageStatsSection) section).getBatteryUsageStats();
if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),
customEnergyConsumerNames)) {
Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
new file mode 100644
index 0000000..b95faac
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -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.server.power.stats;
+
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+
+class BatteryUsageStatsSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "battery-usage-stats";
+
+ private final BatteryUsageStats mBatteryUsageStats;
+
+ BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) {
+ super(TYPE);
+ mBatteryUsageStats = batteryUsageStats;
+ }
+
+ public BatteryUsageStats getBatteryUsageStats() {
+ return mBatteryUsageStats;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ mBatteryUsageStats.writeXml(serializer);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ mBatteryUsageStats.dump(ipw, "");
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
deleted file mode 100644
index 0d7a140..0000000
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.LongArray;
-import android.util.Slog;
-import android.util.Xml;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A storage mechanism for BatteryUsageStats snapshots.
- */
-public class BatteryUsageStatsStore {
- private static final String TAG = "BatteryUsageStatsStore";
-
- private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of(
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .includeProcessStateData()
- .build());
- private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats";
- private static final String SNAPSHOT_FILE_EXTENSION = ".bus";
- private static final String DIR_LOCK_FILENAME = ".lock";
- private static final String CONFIG_FILENAME = "config";
- private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY =
- "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP";
- private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024;
-
- private final Context mContext;
- private final BatteryStatsImpl mBatteryStats;
- private boolean mSystemReady;
- private final File mStoreDir;
- private final File mLockFile;
- private final ReentrantLock mFileLock = new ReentrantLock();
- private FileLock mJvmLock;
- private final AtomicFile mConfigFile;
- private final long mMaxStorageBytes;
- private final Handler mHandler;
- private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
-
- public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir,
- Handler handler) {
- this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- }
-
- @VisibleForTesting
- public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir,
- Handler handler, long maxStorageBytes) {
- mContext = context;
- mBatteryStats = batteryStats;
- mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR);
- mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
- mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME));
- mHandler = handler;
- mMaxStorageBytes = maxStorageBytes;
- mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset);
- mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats);
- }
-
- /**
- * Notifies BatteryUsageStatsStore that the system server is ready.
- */
- public void onSystemReady() {
- mSystemReady = true;
- }
-
- private void prepareForBatteryStatsReset(int resetReason) {
- if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) {
- return;
- }
-
- final List<BatteryUsageStats> stats =
- mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY);
- if (stats.isEmpty()) {
- Slog.wtf(TAG, "No battery usage stats generated");
- return;
- }
-
- mHandler.post(() -> storeBatteryUsageStats(stats.get(0)));
- }
-
- private void storeBatteryUsageStats(BatteryUsageStats stats) {
- lockSnapshotDirectory();
- try {
- if (!mStoreDir.exists()) {
- if (!mStoreDir.mkdirs()) {
- Slog.e(TAG, "Could not create a directory for battery usage stats snapshots");
- return;
- }
- }
- File file = makeSnapshotFilename(stats.getStatsEndTimestamp());
- try {
- writeXmlFileLocked(stats, file);
- } catch (Exception e) {
- Slog.e(TAG, "Cannot save battery usage stats", e);
- }
-
- removeOldSnapshotsLocked();
- } finally {
- unlockSnapshotDirectory();
- }
- }
-
- /**
- * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds
- * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}.
- */
- public long[] listBatteryUsageStatsTimestamps() {
- LongArray timestamps = new LongArray(100);
- lockSnapshotDirectory();
- try {
- for (File file : mStoreDir.listFiles()) {
- String fileName = file.getName();
- if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) {
- try {
- String fileNameWithoutExtension = fileName.substring(0,
- fileName.length() - SNAPSHOT_FILE_EXTENSION.length());
- timestamps.add(Long.parseLong(fileNameWithoutExtension));
- } catch (NumberFormatException e) {
- Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: "
- + fileName);
- }
- }
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return timestamps.toArray();
- }
-
- /**
- * Reads the specified snapshot of BatteryUsageStats. Returns null if the snapshot
- * does not exist.
- */
- @Nullable
- public BatteryUsageStats loadBatteryUsageStats(long timestamp) {
- lockSnapshotDirectory();
- try {
- File file = makeSnapshotFilename(timestamp);
- try {
- return readXmlFileLocked(file);
- } catch (Exception e) {
- Slog.e(TAG, "Cannot read battery usage stats", e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return null;
- }
-
- /**
- * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull
- * in persistent file.
- */
- public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) {
- Properties props = new Properties();
- lockSnapshotDirectory();
- try {
- try (InputStream in = mConfigFile.openRead()) {
- props.load(in);
- } catch (IOException e) {
- Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
- }
- props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY,
- String.valueOf(timestamp));
- FileOutputStream out = null;
- try {
- out = mConfigFile.startWrite();
- props.store(out, "Statsd atom pull timestamps");
- mConfigFile.finishWrite(out);
- } catch (IOException e) {
- mConfigFile.failWrite(out);
- Slog.e(TAG, "Cannot save config file " + mConfigFile, e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- }
-
- /**
- * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET
- * statsd atom pull.
- */
- public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() {
- Properties props = new Properties();
- lockSnapshotDirectory();
- try {
- try (InputStream in = mConfigFile.openRead()) {
- props.load(in);
- } catch (IOException e) {
- Slog.e(TAG, "Cannot load config file " + mConfigFile, e);
- }
- } finally {
- unlockSnapshotDirectory();
- }
- return Long.parseLong(
- props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0"));
- }
-
- private void lockSnapshotDirectory() {
- mFileLock.lock();
-
- // Lock the directory from access by other JVMs
- try {
- mLockFile.getParentFile().mkdirs();
- mLockFile.createNewFile();
- mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
- } catch (IOException e) {
- Log.e(TAG, "Cannot lock snapshot directory", e);
- }
- }
-
- private void unlockSnapshotDirectory() {
- try {
- mJvmLock.close();
- } catch (IOException e) {
- Log.e(TAG, "Cannot unlock snapshot directory", e);
- } finally {
- mFileLock.unlock();
- }
- }
-
- /**
- * Creates a file name by formatting the timestamp as 19-digit zero-padded number.
- * This ensures that sorted directory list follows the chronological order.
- */
- private File makeSnapshotFilename(long statsEndTimestamp) {
- return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp)
- + SNAPSHOT_FILE_EXTENSION);
- }
-
- private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException {
- try (OutputStream out = new FileOutputStream(file)) {
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- stats.writeXml(serializer);
- serializer.endDocument();
- }
- }
-
- private BatteryUsageStats readXmlFileLocked(File file)
- throws IOException, XmlPullParserException {
- try (InputStream in = new FileInputStream(file)) {
- TypedXmlPullParser parser = Xml.newBinaryPullParser();
- parser.setInput(in, StandardCharsets.UTF_8.name());
- return BatteryUsageStats.createFromXml(parser);
- }
- }
-
- private void removeOldSnapshotsLocked() {
- // Read the directory list into a _sorted_ map. The alphanumeric ordering
- // corresponds to the historical order of snapshots because the file names
- // are timestamps zero-padded to the same length.
- long totalSize = 0;
- TreeMap<File, Long> mFileSizes = new TreeMap<>();
- for (File file : mStoreDir.listFiles()) {
- final long fileSize = file.length();
- totalSize += fileSize;
- if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
- mFileSizes.put(file, fileSize);
- }
- }
-
- while (totalSize > mMaxStorageBytes) {
- final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
- if (entry == null) {
- break;
- }
-
- File file = entry.getKey();
- if (!file.delete()) {
- Slog.e(TAG, "Cannot delete battery usage stats " + file);
- }
- totalSize -= entry.getValue();
- mFileSizes.remove(file);
- }
- }
-
- public void removeAllSnapshots() {
- lockSnapshotDirectory();
- try {
- for (File file : mStoreDir.listFiles()) {
- if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) {
- if (!file.delete()) {
- Slog.e(TAG, "Cannot delete battery usage stats " + file);
- }
- }
- }
- } finally {
- unlockSnapshotDirectory();
- }
- }
-}
diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
index 5b3fe06..fbf6928 100644
--- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java
@@ -16,14 +16,8 @@
package com.android.server.power.stats;
-import android.os.BatteryConsumer;
-
-import com.android.internal.os.MultiStateStats;
-
class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats {
-
- CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates,
- MultiStateStats.States[] uidStates) {
- super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates);
+ CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ super(config);
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 686268f..05c0a13 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -21,7 +21,13 @@
import com.android.internal.os.MultiStateStats;
import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.Collection;
/**
@@ -31,6 +37,12 @@
* as part of the {@link PowerStats.Descriptor}.
*/
class PowerComponentAggregatedPowerStats {
+ static final String XML_TAG_POWER_COMPONENT = "power_component";
+ static final String XML_ATTR_ID = "id";
+ private static final String XML_TAG_DEVICE_STATS = "device-stats";
+ private static final String XML_TAG_UID_STATS = "uid-stats";
+ private static final String XML_ATTR_UID = "uid";
+
public final int powerComponentId;
private final MultiStateStats.States[] mDeviceStateConfig;
private final MultiStateStats.States[] mUidStateConfig;
@@ -49,22 +61,24 @@
public MultiStateStats stats;
}
- PowerComponentAggregatedPowerStats(int powerComponentId,
- MultiStateStats.States[] deviceStates,
- MultiStateStats.States[] uidStates) {
- this.powerComponentId = powerComponentId;
- mDeviceStateConfig = deviceStates;
- mUidStateConfig = uidStates;
+ PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) {
+ this.powerComponentId = config.getPowerComponentId();
+ mDeviceStateConfig = config.getDeviceStateConfig();
+ mUidStateConfig = config.getUidStateConfig();
mDeviceStates = new int[mDeviceStateConfig.length];
mDeviceStateTimestamps = new long[mDeviceStateConfig.length];
}
- void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) {
+ public PowerStats.Descriptor getPowerStatsDescriptor() {
+ return mPowerStatsDescriptor;
+ }
+
+ void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {
mDeviceStates[stateId] = state;
mDeviceStateTimestamps[stateId] = time;
if (mDeviceStateConfig[stateId].isTracked()) {
- if (mDeviceStats != null || createDeviceStats()) {
+ if (mDeviceStats != null) {
mDeviceStats.setState(stateId, state, time);
}
}
@@ -72,14 +86,14 @@
if (mUidStateConfig[stateId].isTracked()) {
for (int i = mUidStats.size() - 1; i >= 0; i--) {
PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
- if (uidStats.stats != null || createUidStats(uidStats)) {
+ if (uidStats.stats != null) {
uidStats.stats.setState(stateId, state, time);
}
}
}
}
- void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state,
+ void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,
long time) {
if (!mUidStateConfig[stateId].isTracked()) {
return;
@@ -89,7 +103,7 @@
uidStats.states[stateId] = state;
uidStats.stateTimestampMs[stateId] = time;
- if (uidStats.stats != null || createUidStats(uidStats)) {
+ if (uidStats.stats != null) {
uidStats.stats.setState(stateId, state, time);
}
}
@@ -102,13 +116,6 @@
mPowerStatsDescriptor = powerStats.descriptor;
if (mDeviceStats == null) {
- if (mStatsFactory == null) {
- mStatsFactory = new MultiStateStats.Factory(
- mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
- mUidStatsFactory = new MultiStateStats.Factory(
- mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
- }
-
createDeviceStats();
}
@@ -183,7 +190,11 @@
private boolean createDeviceStats() {
if (mStatsFactory == null) {
- return false;
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ mStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);
}
mDeviceStats = mStatsFactory.create();
@@ -196,7 +207,11 @@
private boolean createUidStats(UidStats uidStats) {
if (mUidStatsFactory == null) {
- return false;
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ mUidStatsFactory = new MultiStateStats.Factory(
+ mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);
}
uidStats.stats = mUidStatsFactory.create();
@@ -211,6 +226,74 @@
return true;
}
+ public void writeXml(TypedXmlSerializer serializer) throws IOException {
+ // No stats aggregated - can skip writing XML altogether
+ if (mPowerStatsDescriptor == null) {
+ return;
+ }
+
+ serializer.startTag(null, XML_TAG_POWER_COMPONENT);
+ serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
+ mPowerStatsDescriptor.writeXml(serializer);
+
+ if (mDeviceStats != null) {
+ serializer.startTag(null, XML_TAG_DEVICE_STATS);
+ mDeviceStats.writeXml(serializer);
+ serializer.endTag(null, XML_TAG_DEVICE_STATS);
+ }
+
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ int uid = mUidStats.keyAt(i);
+ UidStats uidStats = mUidStats.valueAt(i);
+ if (uidStats.stats != null) {
+ serializer.startTag(null, XML_TAG_UID_STATS);
+ serializer.attributeInt(null, XML_ATTR_UID, uid);
+ uidStats.stats.writeXml(serializer);
+ serializer.endTag(null, XML_TAG_UID_STATS);
+ }
+ }
+
+ serializer.endTag(null, XML_TAG_POWER_COMPONENT);
+ serializer.flush();
+ }
+
+ public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ switch (parser.getName()) {
+ case PowerStats.Descriptor.XML_TAG_DESCRIPTOR:
+ mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser);
+ if (mPowerStatsDescriptor == null) {
+ return false;
+ }
+ break;
+ case XML_TAG_DEVICE_STATS:
+ if (mDeviceStats == null) {
+ createDeviceStats();
+ }
+ if (!mDeviceStats.readFromXml(parser)) {
+ return false;
+ }
+ break;
+ case XML_TAG_UID_STATS:
+ int uid = parser.getAttributeInt(null, XML_ATTR_UID);
+ UidStats uidStats = getUidStats(uid);
+ if (uidStats.stats == null) {
+ createUidStats(uidStats);
+ }
+ if (!uidStats.stats.readFromXml(parser)) {
+ return false;
+ }
+ break;
+ }
+ }
+ eventType = parser.next();
+ }
+ return true;
+ }
+
void dumpDevice(IndentingPrintWriter ipw) {
if (mDeviceStats != null) {
ipw.println(mPowerStatsDescriptor.name);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 6a1c1da..f374fb7 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -15,59 +15,26 @@
*/
package com.android.server.power.stats;
-import android.annotation.IntDef;
-import android.os.BatteryConsumer;
import android.os.BatteryStats;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
-import com.android.internal.os.MultiStateStats;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
import java.util.function.Consumer;
-class PowerStatsAggregator {
- public static final int STATE_POWER = 0;
- public static final int STATE_SCREEN = 1;
- public static final int STATE_PROCESS_STATE = 2;
-
- @IntDef({
- STATE_POWER,
- STATE_SCREEN,
- STATE_PROCESS_STATE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrackedState {
- }
-
- static final int POWER_STATE_BATTERY = 0;
- static final int POWER_STATE_OTHER = 1; // Plugged in, or on wireless charger, etc.
- static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"};
-
- static final int SCREEN_STATE_ON = 0;
- static final int SCREEN_STATE_OTHER = 1; // Off, doze etc
- static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"};
-
- static final String[] STATE_LABELS_PROCESS_STATE;
-
- static {
- String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT];
- for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) {
- procStateLabels[i] = BatteryConsumer.processStateToString(i);
- }
- STATE_LABELS_PROCESS_STATE = procStateLabels;
- }
-
- private final BatteryStatsHistory mHistory;
+/**
+ * Power stats aggregator. It reads through portions of battery stats history, finds
+ * relevant items (state changes, power stats etc) and produces one or more
+ * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history.
+ */
+public class PowerStatsAggregator {
private final AggregatedPowerStats mStats;
+ private final BatteryStatsHistory mHistory;
- private PowerStatsAggregator(BatteryStatsHistory history,
- AggregatedPowerStats aggregatedPowerStats) {
+ public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
+ BatteryStatsHistory history) {
+ mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);
mHistory = history;
- mStats = aggregatedPowerStats;
}
/**
@@ -82,12 +49,10 @@
* Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
* the stats in the <code>accept</code> method and never cache it.
*/
- void aggregateBatteryStats(long startTimeMs, long endTimeMs,
+ public void aggregatePowerStats(long startTimeMs, long endTimeMs,
Consumer<AggregatedPowerStats> consumer) {
- mStats.reset();
-
- int currentBatteryState = POWER_STATE_BATTERY;
- int currentScreenState = SCREEN_STATE_OTHER;
+ int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+ int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
long baseTime = -1;
long lastTime = 0;
try (BatteryStatsHistoryIterator iterator =
@@ -96,131 +61,60 @@
BatteryStats.HistoryItem item = iterator.next();
if (baseTime < 0) {
- mStats.setStartTime(item.currentTime);
+ mStats.addClockUpdate(item.time, item.currentTime);
baseTime = item.time;
+ } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME
+ || item.cmd == BatteryStats.HistoryItem.CMD_RESET) {
+ mStats.addClockUpdate(item.time, item.currentTime);
}
lastTime = item.time;
int batteryState =
(item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0
- ? POWER_STATE_OTHER : POWER_STATE_BATTERY;
+ ? AggregatedPowerStatsConfig.POWER_STATE_OTHER
+ : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
if (batteryState != currentBatteryState) {
- mStats.setDeviceState(STATE_POWER, batteryState, item.time);
+ mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState,
+ item.time);
currentBatteryState = batteryState;
}
int screenState =
(item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0
- ? SCREEN_STATE_ON : SCREEN_STATE_OTHER;
+ ? AggregatedPowerStatsConfig.SCREEN_STATE_ON
+ : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
if (screenState != currentScreenState) {
- mStats.setDeviceState(STATE_SCREEN, screenState, item.time);
+ mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState,
+ item.time);
currentScreenState = screenState;
}
if (item.processStateChange != null) {
- mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE,
+ mStats.setUidState(item.processStateChange.uid,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
item.processStateChange.processState, item.time);
}
if (item.powerStats != null) {
if (!mStats.isCompatible(item.powerStats)) {
- mStats.setDuration(lastTime - baseTime);
- consumer.accept(mStats);
+ if (lastTime > baseTime) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
+ }
mStats.reset();
- mStats.setStartTime(item.currentTime);
+ mStats.addClockUpdate(item.time, item.currentTime);
baseTime = lastTime = item.time;
}
mStats.addPowerStats(item.powerStats, item.time);
}
}
}
- mStats.setDuration(lastTime - baseTime);
- consumer.accept(mStats);
- }
-
- static class Builder {
- static class PowerComponentAggregateStatsBuilder {
- private final int mPowerComponentId;
- private @TrackedState int[] mTrackedDeviceStates;
- private @TrackedState int[] mTrackedUidStates;
-
- PowerComponentAggregateStatsBuilder(int powerComponentId) {
- this.mPowerComponentId = powerComponentId;
- }
-
- public PowerComponentAggregateStatsBuilder trackDeviceStates(
- @TrackedState int... states) {
- mTrackedDeviceStates = states;
- return this;
- }
-
- public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) {
- mTrackedUidStates = states;
- return this;
- }
-
- private PowerComponentAggregatedPowerStats build() {
- MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{
- new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER),
- PowerStatsAggregator.STATE_LABELS_POWER),
- new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN),
- PowerStatsAggregator.STATE_LABELS_SCREEN),
- };
-
- MultiStateStats.States[] uidStates = new MultiStateStats.States[]{
- new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER),
- PowerStatsAggregator.STATE_LABELS_POWER),
- new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN),
- PowerStatsAggregator.STATE_LABELS_SCREEN),
- new MultiStateStats.States(
- isTracked(mTrackedUidStates, STATE_PROCESS_STATE),
- PowerStatsAggregator.STATE_LABELS_PROCESS_STATE),
- };
-
- switch (mPowerComponentId) {
- case BatteryConsumer.POWER_COMPONENT_CPU:
- return new CpuAggregatedPowerStats(deviceStates, uidStates);
- default:
- return new PowerComponentAggregatedPowerStats(mPowerComponentId,
- deviceStates, uidStates);
- }
- }
-
- private boolean isTracked(int[] trackedStates, int state) {
- if (trackedStates == null) {
- return false;
- }
-
- for (int trackedState : trackedStates) {
- if (trackedState == state) {
- return true;
- }
- }
- return false;
- }
+ if (lastTime > baseTime) {
+ mStats.setDuration(lastTime - baseTime);
+ consumer.accept(mStats);
}
- private final BatteryStatsHistory mHistory;
- private final List<PowerComponentAggregateStatsBuilder> mPowerComponents =
- new ArrayList<>();
-
- Builder(BatteryStatsHistory history) {
- mHistory = history;
- }
-
- PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) {
- PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder(
- powerComponentId);
- mPowerComponents.add(builder);
- return builder;
- }
-
- PowerStatsAggregator build() {
- return new PowerStatsAggregator(mHistory, new AggregatedPowerStats(
- mPowerComponents.stream()
- .map(PowerComponentAggregateStatsBuilder::build)
- .toArray(PowerComponentAggregatedPowerStats[]::new)));
- }
+ mStats.reset(); // to free up memory
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
new file mode 100644
index 0000000..58619c7
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.annotation.DurationMillisLong;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+
+import java.io.PrintWriter;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
+ * {@link PowerStatsStore}.
+ */
+public class PowerStatsScheduler {
+ private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
+ private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
+
+ private final Context mContext;
+ private boolean mEnablePeriodicPowerStatsCollection;
+ @DurationMillisLong
+ private final long mAggregatedPowerStatsSpanDuration;
+ @DurationMillisLong
+ private final long mPowerStatsAggregationPeriod;
+ private final PowerStatsStore mPowerStatsStore;
+ private final Clock mClock;
+ private final MonotonicClock mMonotonicClock;
+ private final Handler mHandler;
+ private final BatteryStatsImpl mBatteryStats;
+ private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private final PowerStatsAggregator mPowerStatsAggregator;
+ private long mLastSavedSpanEndMonotonicTime;
+
+ public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+ @DurationMillisLong long aggregatedPowerStatsSpanDuration,
+ @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
+ Clock clock, MonotonicClock monotonicClock, Handler handler,
+ BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) {
+ mContext = context;
+ mPowerStatsAggregator = powerStatsAggregator;
+ mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
+ mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
+ mPowerStatsStore = powerStatsStore;
+ mClock = clock;
+ mMonotonicClock = monotonicClock;
+ mHandler = handler;
+ mBatteryStats = batteryStats;
+ mBatteryUsageStatsProvider = batteryUsageStatsProvider;
+ }
+
+ /**
+ * Kicks off the scheduling of power stats aggregation spans.
+ */
+ public void start(boolean enablePeriodicPowerStatsCollection) {
+ mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset);
+ mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection;
+ if (mEnablePeriodicPowerStatsCollection) {
+ scheduleNextPowerStatsAggregation();
+ }
+ }
+
+ private void scheduleNextPowerStatsAggregation() {
+ AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME,
+ mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+ () -> {
+ schedulePowerStatsAggregation();
+ mHandler.post(this::scheduleNextPowerStatsAggregation);
+ }, mHandler);
+ }
+
+ /**
+ * Initiate an asynchronous process of aggregation of power stats.
+ */
+ @VisibleForTesting
+ public void schedulePowerStatsAggregation() {
+ // Catch up the power stats collectors
+ mBatteryStats.schedulePowerStatsSampleCollection();
+ mHandler.post(this::aggregateAndStorePowerStats);
+ }
+
+ private void aggregateAndStorePowerStats() {
+ long currentTimeMillis = mClock.currentTimeMillis();
+ long currentMonotonicTime = mMonotonicClock.monotonicTime();
+ long startTime = getLastSavedSpanEndMonotonicTime();
+ long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
+ mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
+ while (endTimeMs <= currentMonotonicTime) {
+ mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
+ stats -> {
+ storeAggregatedPowerStats(stats);
+ mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
+ });
+
+ startTime = endTimeMs;
+ endTimeMs += mAggregatedPowerStatsSpanDuration;
+ }
+ }
+
+ /**
+ * Performs a power stats aggregation pass and then dumps all stored aggregated power stats
+ * spans followed by the remainder that has not been stored yet.
+ */
+ public void aggregateAndDumpPowerStats(PrintWriter pw) {
+ if (mHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("Should not be executed on the bg handler thread.");
+ }
+
+ schedulePowerStatsAggregation();
+
+ // Wait for the aggregation process to finish storing aggregated stats spans in the store.
+ awaitCompletion();
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ mHandler.post(() -> {
+ mPowerStatsStore.dump(ipw);
+ // Aggregate the remainder of power stats and dump the results without storing them yet.
+ long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
+ mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0,
+ stats -> {
+ // Create a PowerStatsSpan for consistency of the textual output
+ PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
+ if (span != null) {
+ span.dump(ipw);
+ }
+ });
+ });
+
+ awaitCompletion();
+ }
+
+ /**
+ * Align the supplied time to the wall clock, for aesthetic purposes. For example, if
+ * the schedule is configured with a 15-min interval, the captured aggregated stats will
+ * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current
+ * time is used for the alignment, so if the wall clock changed during an aggregation span,
+ * or if the device was off (which stops the monotonic clock), the alignment may be
+ * temporarily broken.
+ */
+ @VisibleForTesting
+ public static long alignToWallClock(long targetMonotonicTime, long interval,
+ long currentMonotonicTime, long currentTimeMillis) {
+
+ // Estimate the wall clock time for the requested targetMonotonicTime
+ long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime);
+
+ if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) {
+ // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc
+
+ // First, round up to the next whole minute
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ // Now set the minute to a multiple of the requested interval
+ int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS);
+ cal.set(Calendar.MINUTE,
+ ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes)
+ * intervalInMinutes);
+
+ long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+ return targetMonotonicTime + adjustment;
+ } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) {
+ // If the interval is a divisor of a day, e.g. 2h, 3h, etc
+
+ // First, round up to the next whole hour
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ // Now set the hour of day to a multiple of the requested interval
+ int intervalInHours = (int) (interval / HOUR_IN_MILLIS);
+ cal.set(Calendar.HOUR_OF_DAY,
+ ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours)
+ * intervalInHours);
+
+ long adjustment = cal.getTimeInMillis() - targetWallClockTime;
+ return targetMonotonicTime + adjustment;
+ }
+
+ return targetMonotonicTime;
+ }
+
+ private long getLastSavedSpanEndMonotonicTime() {
+ if (mLastSavedSpanEndMonotonicTime != 0) {
+ return mLastSavedSpanEndMonotonicTime;
+ }
+
+ for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
+ if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+ for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
+ long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
+ if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
+ mLastSavedSpanEndMonotonicTime = endMonotonicTime;
+ }
+ }
+ }
+ }
+ return mLastSavedSpanEndMonotonicTime;
+ }
+
+ private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+ mPowerStatsStore.storeAggregatedPowerStats(stats);
+ }
+
+ private void storeBatteryUsageStatsOnReset(int resetReason) {
+ if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) {
+ return;
+ }
+
+ final BatteryUsageStats batteryUsageStats =
+ mBatteryUsageStatsProvider.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includePowerModels()
+ .includeProcessStateData()
+ .build());
+
+ // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end
+ // Once that change is made, we will be able to use the BatteryUsageStats' monotonic
+ // start time
+ long monotonicStartTime =
+ mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration();
+ mHandler.post(() ->
+ mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats));
+ }
+
+ private void awaitCompletion() {
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
new file mode 100644
index 0000000..3b260ca
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -0,0 +1,433 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import com.google.android.collect.Sets;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Contains power stats of various kinds, aggregated over a time span.
+ */
+public class PowerStatsSpan {
+ private static final String TAG = "PowerStatsStore";
+
+ /**
+ * Increment VERSION when the XML format of the store changes. Also, update
+ * {@link #isCompatibleXmlFormat} to return true for all legacy versions
+ * that are compatible with the new one.
+ */
+ private static final int VERSION = 1;
+
+ private static final String XML_TAG_METADATA = "metadata";
+ private static final String XML_ATTR_ID = "id";
+ private static final String XML_ATTR_VERSION = "version";
+ private static final String XML_TAG_TIMEFRAME = "timeframe";
+ private static final String XML_ATTR_MONOTONIC = "monotonic";
+ private static final String XML_ATTR_START_TIME = "start";
+ private static final String XML_ATTR_DURATION = "duration";
+ private static final String XML_TAG_SECTION = "section";
+ private static final String XML_ATTR_SECTION_TYPE = "type";
+
+ private static final DateTimeFormatter DATE_FORMAT =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
+ static class TimeFrame {
+ public final long startMonotonicTime;
+ @CurrentTimeMillisLong
+ public final long startTime;
+ @DurationMillisLong
+ public final long duration;
+
+ TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime,
+ @DurationMillisLong long duration) {
+ this.startMonotonicTime = startMonotonicTime;
+ this.startTime = startTime;
+ this.duration = duration;
+ }
+
+ void write(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_TIMEFRAME);
+ serializer.attributeLong(null, XML_ATTR_START_TIME, startTime);
+ serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime);
+ serializer.attributeLong(null, XML_ATTR_DURATION, duration);
+ serializer.endTag(null, XML_TAG_TIMEFRAME);
+ }
+
+ static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException {
+ return new TimeFrame(
+ parser.getAttributeLong(null, XML_ATTR_MONOTONIC),
+ parser.getAttributeLong(null, XML_ATTR_START_TIME),
+ parser.getAttributeLong(null, XML_ATTR_DURATION));
+ }
+
+ /**
+ * Prints the contents of this TimeFrame.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime)))
+ .append(" (monotonic=").append(startMonotonicTime).append(") ")
+ .append(" duration=");
+ String durationString = TimeUtils.formatDuration(duration);
+ if (durationString.startsWith("+")) {
+ sb.append(durationString.substring(1));
+ } else {
+ sb.append(durationString);
+ }
+ pw.print(sb);
+ }
+ }
+
+ static class Metadata {
+ static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId);
+
+ private final long mId;
+ private final List<TimeFrame> mTimeFrames = new ArrayList<>();
+ private final List<String> mSections = new ArrayList<>();
+
+ Metadata(long id) {
+ mId = id;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public List<TimeFrame> getTimeFrames() {
+ return mTimeFrames;
+ }
+
+ public List<String> getSections() {
+ return mSections;
+ }
+
+ void addTimeFrame(TimeFrame timeFrame) {
+ mTimeFrames.add(timeFrame);
+ }
+
+ void addSection(String sectionType) {
+ // The number of sections per span is small, so there is no need to use a Set
+ if (!mSections.contains(sectionType)) {
+ mSections.add(sectionType);
+ }
+ }
+
+ void write(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(null, XML_TAG_METADATA);
+ serializer.attributeLong(null, XML_ATTR_ID, mId);
+ serializer.attributeInt(null, XML_ATTR_VERSION, VERSION);
+ for (TimeFrame timeFrame : mTimeFrames) {
+ timeFrame.write(serializer);
+ }
+ for (String section : mSections) {
+ serializer.startTag(null, XML_TAG_SECTION);
+ serializer.attribute(null, XML_ATTR_SECTION_TYPE, section);
+ serializer.endTag(null, XML_TAG_SECTION);
+ }
+ serializer.endTag(null, XML_TAG_METADATA);
+ }
+
+ /**
+ * Reads just the header of the XML file containing metadata.
+ * Returns null if the file does not contain a compatible <metadata> element.
+ */
+ @Nullable
+ public static Metadata read(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ Metadata metadata = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_METADATA))) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if (tagName.equals(XML_TAG_METADATA)) {
+ int version = parser.getAttributeInt(null, XML_ATTR_VERSION);
+ if (!isCompatibleXmlFormat(version)) {
+ Slog.e(TAG,
+ "Incompatible version " + version + "; expected " + VERSION);
+ return null;
+ }
+
+ long id = parser.getAttributeLong(null, XML_ATTR_ID);
+ metadata = new Metadata(id);
+ } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) {
+ metadata.addTimeFrame(TimeFrame.read(parser));
+ } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) {
+ metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE));
+ }
+ }
+ eventType = parser.next();
+ }
+ return metadata;
+ }
+
+ /**
+ * Prints the metadata.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ dump(pw, true);
+ }
+
+ void dump(IndentingPrintWriter pw, boolean includeSections) {
+ pw.print("Span ");
+ if (mTimeFrames.size() > 0) {
+ mTimeFrames.get(0).dump(pw);
+ pw.println();
+ }
+
+ // Sometimes, when the wall clock is adjusted in the middle of a stats session,
+ // we will have more than one time frame.
+ for (int i = 1; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ pw.print(" "); // Aligned below "Span "
+ timeFrame.dump(pw);
+ pw.println();
+ }
+
+ if (includeSections) {
+ pw.increaseIndent();
+ for (String section : mSections) {
+ pw.print("section", section);
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter ipw = new IndentingPrintWriter(sw);
+ ipw.print("id", mId);
+ for (int i = 0; i < mTimeFrames.size(); i++) {
+ TimeFrame timeFrame = mTimeFrames.get(i);
+ ipw.print("timeframe=[");
+ timeFrame.dump(ipw);
+ ipw.print("] ");
+ }
+ for (String section : mSections) {
+ ipw.print("section", section);
+ }
+ ipw.flush();
+ return sw.toString().trim();
+ }
+ }
+
+ /**
+ * Contains a specific type of aggregate power stats. The contents type is determined by
+ * the section type.
+ */
+ public abstract static class Section {
+ private final String mType;
+
+ Section(String type) {
+ mType = type;
+ }
+
+ /**
+ * Returns the section type, which determines the type of data stored in the corresponding
+ * section of {@link PowerStatsSpan}
+ */
+ public String getType() {
+ return mType;
+ }
+
+ abstract void write(TypedXmlSerializer serializer) throws IOException;
+
+ /**
+ * Prints the section type.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println(mType);
+ }
+ }
+
+ /**
+ * A universal XML parser for {@link PowerStatsSpan.Section}'s. It is aware of all
+ * supported section types as well as their corresponding XML formats.
+ */
+ public interface SectionReader {
+ /**
+ * Reads the contents of the section using the parser. The type of the object
+ * read and the corresponding XML format are determined by the section type.
+ */
+ Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException;
+ }
+
+ private final Metadata mMetadata;
+ private final List<Section> mSections = new ArrayList<>();
+
+ public PowerStatsSpan(long id) {
+ this(new Metadata(id));
+ }
+
+ private PowerStatsSpan(Metadata metadata) {
+ mMetadata = metadata;
+ }
+
+ public Metadata getMetadata() {
+ return mMetadata;
+ }
+
+ public long getId() {
+ return mMetadata.mId;
+ }
+
+ void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
+ @DurationMillisLong long duration) {
+ mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration));
+ }
+
+ void addSection(Section section) {
+ mMetadata.addSection(section.getType());
+ mSections.add(section);
+ }
+
+ @NonNull
+ public List<Section> getSections() {
+ return mSections;
+ }
+
+ private static boolean isCompatibleXmlFormat(int version) {
+ return version == VERSION;
+ }
+
+ /**
+ * Creates an XML file containing the persistent state of the power stats span.
+ */
+ @VisibleForTesting
+ public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ mMetadata.write(serializer);
+ for (Section section : mSections) {
+ serializer.startTag(null, XML_TAG_SECTION);
+ serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType);
+ section.write(serializer);
+ serializer.endTag(null, XML_TAG_SECTION);
+ }
+ serializer.endDocument();
+ }
+
+ @Nullable
+ static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser,
+ SectionReader sectionReader, String... sectionTypes)
+ throws IOException, XmlPullParserException {
+ Set<String> neededSections = Sets.newArraySet(sectionTypes);
+ boolean selectSections = !neededSections.isEmpty();
+ parser.setInput(in, StandardCharsets.UTF_8.name());
+
+ Metadata metadata = Metadata.read(parser);
+ if (metadata == null) {
+ return null;
+ }
+
+ PowerStatsSpan span = new PowerStatsSpan(metadata);
+ boolean skipSection = false;
+ int nestingLevel = 0;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (skipSection) {
+ if (eventType == XmlPullParser.END_TAG
+ && parser.getName().equals(XML_TAG_SECTION)) {
+ nestingLevel--;
+ if (nestingLevel == 0) {
+ skipSection = false;
+ }
+ } else if (eventType == XmlPullParser.START_TAG
+ && parser.getName().equals(XML_TAG_SECTION)) {
+ nestingLevel++;
+ }
+ } else if (eventType == XmlPullParser.START_TAG) {
+ String tag = parser.getName();
+ if (tag.equals(XML_TAG_SECTION)) {
+ String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE);
+ if (!selectSections || neededSections.contains(sectionType)) {
+ Section section = sectionReader.read(sectionType, parser);
+ if (section == null) {
+ if (selectSections) {
+ throw new XmlPullParserException(
+ "Unsupported PowerStatsStore section type: " + sectionType);
+ } else {
+ section = new Section(sectionType) {
+ @Override
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("Unsupported PowerStatsStore section type: "
+ + sectionType);
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) {
+ }
+ };
+ }
+ }
+ span.addSection(section);
+ } else {
+ skipSection = true;
+ }
+ } else if (tag.equals(XML_TAG_METADATA)) {
+ Metadata.read(parser);
+ }
+ }
+ eventType = parser.next();
+ }
+ return span;
+ }
+
+ /**
+ * Prints the contents of this power stats span.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ mMetadata.dump(ipw, /* includeSections */ false);
+ for (Section section : mSections) {
+ ipw.increaseIndent();
+ ipw.println(section.mType);
+ section.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
new file mode 100644
index 0000000..7123bcb
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -0,0 +1,371 @@
+/*
+ * 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.server.power.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.BatteryUsageStats;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A storage mechanism for aggregated power/battery stats.
+ */
+public class PowerStatsStore {
+ private static final String TAG = "PowerStatsStore";
+
+ private static final String POWER_STATS_DIR = "power-stats";
+ private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss";
+ private static final String DIR_LOCK_FILENAME = ".lock";
+ private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024;
+
+ private final File mSystemDir;
+ private final File mStoreDir;
+ private final File mLockFile;
+ private final ReentrantLock mFileLock = new ReentrantLock();
+ private FileLock mJvmLock;
+ private final long mMaxStorageBytes;
+ private final Handler mHandler;
+ private final PowerStatsSpan.SectionReader mSectionReader;
+ private volatile List<PowerStatsSpan.Metadata> mTableOfContents;
+
+ public PowerStatsStore(@NonNull File systemDir, Handler handler,
+ AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler,
+ new DefaultSectionReader(aggregatedPowerStatsConfig));
+ }
+
+ @VisibleForTesting
+ public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler,
+ @NonNull PowerStatsSpan.SectionReader sectionReader) {
+ mSystemDir = systemDir;
+ mStoreDir = new File(systemDir, POWER_STATS_DIR);
+ mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
+ mHandler = handler;
+ mMaxStorageBytes = maxStorageBytes;
+ mSectionReader = sectionReader;
+ mHandler.post(this::maybeClearLegacyStore);
+ }
+
+ /**
+ * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store.
+ */
+ @NonNull
+ public List<PowerStatsSpan.Metadata> getTableOfContents() {
+ List<PowerStatsSpan.Metadata> toc = mTableOfContents;
+ if (toc != null) {
+ return toc;
+ }
+
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ lockStoreDirectory();
+ try {
+ toc = new ArrayList<>();
+ for (File file : mStoreDir.listFiles()) {
+ String fileName = file.getName();
+ if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ continue;
+ }
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser);
+ if (metadata != null) {
+ toc.add(metadata);
+ } else {
+ Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName);
+ file.delete();
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName);
+ }
+ }
+ toc.sort(PowerStatsSpan.Metadata.COMPARATOR);
+ mTableOfContents = Collections.unmodifiableList(toc);
+ } finally {
+ unlockStoreDirectory();
+ }
+
+ return toc;
+ }
+
+ /**
+ * Saves the specified span in the store.
+ */
+ public void storePowerStatsSpan(PowerStatsSpan span) {
+ maybeClearLegacyStore();
+ lockStoreDirectory();
+ try {
+ if (!mStoreDir.exists()) {
+ if (!mStoreDir.mkdirs()) {
+ Slog.e(TAG, "Could not create a directory for power stats store");
+ return;
+ }
+ }
+
+ AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId()));
+ file.write(out-> {
+ try {
+ span.writeXml(out, Xml.newBinarySerializer());
+ } catch (Exception e) {
+ // AtomicFile will log the exception and delete the file.
+ throw new RuntimeException(e);
+ }
+ });
+ mTableOfContents = null;
+ removeOldSpansLocked();
+ } finally {
+ unlockStoreDirectory();
+ }
+ }
+
+ /**
+ * Loads the PowerStatsSpan identified by its ID. Only loads the sections with
+ * the specified types. Loads all sections if no sectionTypes is empty.
+ */
+ @Nullable
+ public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) {
+ TypedXmlPullParser parser = Xml.newBinaryPullParser();
+ lockStoreDirectory();
+ try {
+ File file = makePowerStatsSpanFilename(id);
+ try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
+ return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file);
+ }
+ } finally {
+ unlockStoreDirectory();
+ }
+ return null;
+ }
+
+ void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+ PowerStatsSpan span = createPowerStatsSpan(stats);
+ if (span == null) {
+ return;
+ }
+ storePowerStatsSpan(span);
+ }
+
+ static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
+ List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+ if (clockUpdates.isEmpty()) {
+ Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
+ return null;
+ }
+
+ long monotonicTime = clockUpdates.get(0).monotonicTime;
+ long durationSum = 0;
+ PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
+ for (int i = 0; i < clockUpdates.size(); i++) {
+ AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
+ long duration;
+ if (i == clockUpdates.size() - 1) {
+ duration = stats.getDuration() - durationSum;
+ } else {
+ duration = clockUpdate.monotonicTime - monotonicTime;
+ }
+ span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
+ monotonicTime = clockUpdate.monotonicTime;
+ durationSum += duration;
+ }
+
+ span.addSection(new AggregatedPowerStatsSection(stats));
+ return span;
+ }
+
+ /**
+ * Stores a {@link PowerStatsSpan} containing a single section for the supplied
+ * battery usage stats.
+ */
+ public void storeBatteryUsageStats(long monotonicStartTime,
+ BatteryUsageStats batteryUsageStats) {
+ PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime);
+ span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(),
+ batteryUsageStats.getStatsDuration());
+ span.addSection(new BatteryUsageStatsSection(batteryUsageStats));
+ storePowerStatsSpan(span);
+ }
+
+ /**
+ * Creates a file name by formatting the span ID as a 19-digit zero-padded number.
+ * This ensures that the lexicographically sorted directory follows the chronological order.
+ */
+ private File makePowerStatsSpanFilename(long id) {
+ return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id)
+ + POWER_STATS_SPAN_FILE_EXTENSION);
+ }
+
+ private void maybeClearLegacyStore() {
+ File legacyStoreDir = new File(mSystemDir, "battery-usage-stats");
+ if (legacyStoreDir.exists()) {
+ FileUtils.deleteContentsAndDir(legacyStoreDir);
+ }
+ }
+
+ private void lockStoreDirectory() {
+ mFileLock.lock();
+
+ // Lock the directory from access by other JVMs
+ try {
+ mLockFile.getParentFile().mkdirs();
+ mLockFile.createNewFile();
+ mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot lock snapshot directory", e);
+ }
+ }
+
+ private void unlockStoreDirectory() {
+ try {
+ mJvmLock.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot unlock snapshot directory", e);
+ } finally {
+ mFileLock.unlock();
+ }
+ }
+
+ private void removeOldSpansLocked() {
+ // Read the directory list into a _sorted_ map. The alphanumeric ordering
+ // corresponds to the historical order of snapshots because the file names
+ // are timestamps zero-padded to the same length.
+ long totalSize = 0;
+ TreeMap<File, Long> mFileSizes = new TreeMap<>();
+ for (File file : mStoreDir.listFiles()) {
+ final long fileSize = file.length();
+ totalSize += fileSize;
+ if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ mFileSizes.put(file, fileSize);
+ }
+ }
+
+ while (totalSize > mMaxStorageBytes) {
+ final Map.Entry<File, Long> entry = mFileSizes.firstEntry();
+ if (entry == null) {
+ break;
+ }
+
+ File file = entry.getKey();
+ if (!file.delete()) {
+ Slog.e(TAG, "Cannot delete power stats span " + file);
+ }
+ totalSize -= entry.getValue();
+ mFileSizes.remove(file);
+ mTableOfContents = null;
+ }
+ }
+
+ /**
+ * Deletes all contents from the store.
+ */
+ public void reset() {
+ lockStoreDirectory();
+ try {
+ for (File file : mStoreDir.listFiles()) {
+ if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) {
+ if (!file.delete()) {
+ Slog.e(TAG, "Cannot delete power stats span " + file);
+ }
+ }
+ }
+ mTableOfContents = List.of();
+ } finally {
+ unlockStoreDirectory();
+ }
+ }
+
+ /**
+ * Prints the summary of contents of the store: only metadata, but not the actual stored
+ * objects.
+ */
+ public void dumpTableOfContents(IndentingPrintWriter ipw) {
+ ipw.println("Power stats store TOC");
+ ipw.increaseIndent();
+ List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+ for (PowerStatsSpan.Metadata metadata : contents) {
+ metadata.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Prints the contents of the store.
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("Power stats store");
+ ipw.increaseIndent();
+ List<PowerStatsSpan.Metadata> contents = getTableOfContents();
+ for (PowerStatsSpan.Metadata metadata : contents) {
+ PowerStatsSpan span = loadPowerStatsSpan(metadata.getId());
+ if (span != null) {
+ span.dump(ipw);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+
+ private static class DefaultSectionReader implements PowerStatsSpan.SectionReader {
+ private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+ DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
+ }
+
+ @Override
+ public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ switch (sectionType) {
+ case AggregatedPowerStatsSection.TYPE:
+ return new AggregatedPowerStatsSection(
+ AggregatedPowerStats.createFromXml(parser,
+ mAggregatedPowerStatsConfig));
+ case BatteryUsageStatsSection.TYPE:
+ return new BatteryUsageStatsSection(
+ BatteryUsageStats.createFromXml(parser));
+ default:
+ return null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3a..c33f3d9 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,11 +1,10 @@
jeffreyhuang@google.com
joeo@google.com
-jtnguyen@google.com
+monicamwang@google.com
muhammadq@google.com
+rayhdez@google.com
rslawik@google.com
-ruchirr@google.com
sharaienko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ebd21d9..a01113b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1316,6 +1316,9 @@
if (mLaunchIntoPipHostActivity != null) {
pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);
}
+ if (mWaitForEnteringPinnedMode) {
+ pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true");
+ }
mLetterboxUiController.dump(pw, prefix);
@@ -3132,9 +3135,7 @@
}
boolean canReceiveKeys() {
- // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent
- return getWindowConfiguration().canReceiveKeys()
- && (task == null || task.getWindowConfiguration().canReceiveKeys());
+ return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
}
boolean isResizeable() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0b67321..de335d3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,6 +68,7 @@
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3686,6 +3687,11 @@
getTransitionController(), mWindowManager.mSyncEngine)
: null;
+ if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
+ && transition != null) {
+ transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
+ }
+
final Runnable enterPipRunnable = () -> {
synchronized (mGlobalLock) {
if (r.getParent() == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12e1e2c..777b5cd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2832,17 +2832,22 @@
static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
private ActivityRecord mStarting;
private boolean mIncludeInvisibleAndFinishing;
+ private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
+ ActivityRecord getOpaqueActivity(
+ @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
mIncludeInvisibleAndFinishing = true;
+ mIgnoringKeyguard = ignoringKeyguard;
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
}
- ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container,
- @Nullable ActivityRecord starting) {
+ ActivityRecord getVisibleOpaqueActivity(
+ @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting,
+ boolean ignoringKeyguard) {
mStarting = starting;
mIncludeInvisibleAndFinishing = false;
+ mIgnoringKeyguard = ignoringKeyguard;
final ActivityRecord opaque = container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
mStarting = null;
@@ -2851,7 +2856,9 @@
@Override
public boolean test(ActivityRecord r) {
- if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) {
+ if (!mIncludeInvisibleAndFinishing && r != mStarting
+ && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard)
+ || (!mIgnoringKeyguard && !r.isVisible()))) {
// Ignore invisible activities that are not the currently starting activity
// (about to be visible).
return false;
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index b039646..3dc377d 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -22,6 +22,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.animation.Animation;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
@@ -31,7 +32,8 @@
* Interface that describes an animation and bridges the animation start to the component
* responsible for running the animation.
*/
-interface AnimationAdapter {
+@VisibleForTesting
+public interface AnimationAdapter {
long STATUS_BAR_TRANSITION_DURATION = 120L;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4237668..6d59b29 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -276,6 +276,10 @@
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
removedWindowContainer = window;
+ } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
+ // skip if current activity is translucent
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ removedWindowContainer = window;
} else if (prevActivity != null) {
if (!isOccluded || prevActivity.canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index ae29afa..64a230e 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -11,172 +11,36 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.server.wm;
-import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
-import static com.android.server.wm.AlphaAnimationSpecProto.TO;
-import static com.android.server.wm.AnimationSpecProto.ALPHA;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-
import android.annotation.NonNull;
import android.graphics.Rect;
-import android.util.Log;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-
-import java.io.PrintWriter;
+import com.android.window.flags.Flags;
/**
* Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
* black layers of varying opacity at various Z-levels which create the effect of a Dim.
*/
-class Dimmer {
- private static final String TAG = "WindowManager";
- // This is in milliseconds.
- private static final int DEFAULT_DIM_ANIM_DURATION = 200;
-
- private class DimAnimatable implements SurfaceAnimator.Animatable {
- private SurfaceControl mDimLayer;
-
- private DimAnimatable(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mHost.getSyncTransaction();
- }
-
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mHost.getPendingTransaction();
- }
-
- @Override
- public void commitPendingTransaction() {
- mHost.commitPendingTransaction();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- }
-
- @Override
- public SurfaceControl.Builder makeAnimationLeash() {
- return mHost.makeAnimationLeash();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public SurfaceControl getSurfaceControl() {
- return mDimLayer;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mHost.getSurfaceControl();
- }
-
- @Override
- public int getSurfaceWidth() {
- // This will determine the size of the leash created. This should be the size of the
- // host and not the dim layer since the dim layer may get bigger during animation. If
- // that occurs, the leash size cannot change so we need to ensure the leash is big
- // enough that the dim layer can grow.
- // This works because the mHost will be a Task which has the display bounds.
- return mHost.getSurfaceWidth();
- }
-
- @Override
- public int getSurfaceHeight() {
- // See getSurfaceWidth() above for explanation.
- return mHost.getSurfaceHeight();
- }
-
- void removeSurface() {
- if (mDimLayer != null && mDimLayer.isValid()) {
- getSyncTransaction().remove(mDimLayer);
- }
- mDimLayer = null;
- }
- }
-
- @VisibleForTesting
- class DimState {
- /**
- * The layer where property changes should be invoked on.
- */
- SurfaceControl mDimLayer;
- boolean mDimming;
- boolean isVisible;
- SurfaceAnimator mSurfaceAnimator;
-
- // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
- final Rect mDimBounds = new Rect();
-
- /**
- * Determines whether the dim layer should animate before destroying.
- */
- boolean mAnimateExit = true;
-
- /**
- * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
- * details on Dim lifecycle.
- */
- boolean mDontReset;
-
- DimState(SurfaceControl dimLayer) {
- mDimLayer = dimLayer;
- mDimming = true;
- final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
- mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
- if (!mDimming) {
- dimAnimatable.removeSurface();
- }
- }, mHost.mWmService);
- }
- }
-
+public abstract class Dimmer {
/**
- * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+ * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
* host, some controller of it, or one of the hosts children.
*/
- private WindowContainer mHost;
- private WindowContainer mLastRequestedDimContainer;
- @VisibleForTesting
- DimState mDimState;
+ protected final WindowContainer mHost;
- private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
-
- @VisibleForTesting
- interface SurfaceAnimatorStarter {
- void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @AnimationType int type);
- }
-
- Dimmer(WindowContainer host) {
- this(host, SurfaceAnimator::startAnimation);
- }
-
- Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+ protected Dimmer(WindowContainer host) {
mHost = host;
- mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+ }
+
+ // Constructs the correct type of dimmer
+ static Dimmer create(WindowContainer host) {
+ return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host);
}
@NonNull
@@ -184,49 +48,8 @@
return mHost;
}
- private SurfaceControl makeDimLayer() {
- return mHost.makeChildSurface(null)
- .setParent(mHost.getSurfaceControl())
- .setColorLayer()
- .setName("Dim Layer for - " + mHost.getName())
- .setCallsite("Dimmer.makeDimLayer")
- .build();
- }
-
- /**
- * Retrieve the DimState, creating one if it doesn't exist.
- */
- private DimState getDimState(WindowContainer container) {
- if (mDimState == null) {
- try {
- final SurfaceControl ctl = makeDimLayer();
- mDimState = new DimState(ctl);
- } catch (Surface.OutOfResourcesException e) {
- Log.w(TAG, "OutOfResourcesException creating dim surface");
- }
- }
-
- mLastRequestedDimContainer = container;
- return mDimState;
- }
-
- private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
- final DimState d = getDimState(container);
-
- if (d == null) {
- return;
- }
-
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- SurfaceControl.Transaction t = mHost.getPendingTransaction();
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- t.setAlpha(d.mDimLayer, alpha);
- t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
-
- d.mDimming = true;
- }
+ protected abstract void dim(
+ WindowContainer container, int relativeLayer, float alpha, int blurRadius);
/**
* Place a dim above the given container, which should be a child of the host container.
@@ -260,25 +83,15 @@
* chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
* a chance to request dims to continue.
*/
- void resetDimStates() {
- if (mDimState == null) {
- return;
- }
- if (!mDimState.mDontReset) {
- mDimState.mDimming = false;
- }
- }
+ abstract void resetDimStates();
/** Returns non-null bounds if the dimmer is showing. */
- Rect getDimBounds() {
- return mDimState != null ? mDimState.mDimBounds : null;
- }
+ abstract Rect getDimBounds();
- void dontAnimateExit() {
- if (mDimState != null) {
- mDimState.mAnimateExit = false;
- }
- }
+ abstract void dontAnimateExit();
+
+ @VisibleForTesting
+ abstract SurfaceControl getDimLayer();
/**
* Call after invoking {@link WindowContainer#prepareSurfaces} on children as
@@ -288,109 +101,5 @@
* @param t A transaction in which to update the dims.
* @return true if any Dims were updated.
*/
- boolean updateDims(SurfaceControl.Transaction t) {
- if (mDimState == null) {
- return false;
- }
-
- if (!mDimState.mDimming) {
- if (!mDimState.mAnimateExit) {
- if (mDimState.mDimLayer.isValid()) {
- t.remove(mDimState.mDimLayer);
- }
- } else {
- startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
- }
- mDimState = null;
- return false;
- } else {
- final Rect bounds = mDimState.mDimBounds;
- // TODO: Once we use geometry from hierarchy this falls away.
- t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
- t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
- if (!mDimState.isVisible) {
- mDimState.isVisible = true;
- t.show(mDimState.mDimLayer);
- // Skip enter animation while starting window is on top of its activity
- final WindowState ws = mLastRequestedDimContainer.asWindowState();
- if (ws == null || ws.mActivityRecord == null
- || ws.mActivityRecord.mStartingData == null) {
- startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t);
- }
- }
- return true;
- }
- }
-
- private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
- }
-
- private void startDimExit(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t) {
- startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
- }
-
- private void startAnim(WindowContainer container, SurfaceAnimator animator,
- SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
- mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
- new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
- mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_DIMMER);
- }
-
- private long getDimDuration(WindowContainer container) {
- // If there's no container, then there isn't an animation occurring while dimming. Set the
- // duration to 0 so it immediately dims to the set alpha.
- if (container == null) {
- return 0;
- }
-
- // Otherwise use the same duration as the animation on the WindowContainer
- AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
- final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
- return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
- : animationAdapter.getDurationHint();
- }
-
- private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
- private final long mDuration;
- private final float mFromAlpha;
- private final float mToAlpha;
-
- AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
- mFromAlpha = fromAlpha;
- mToAlpha = toAlpha;
- mDuration = duration;
- }
-
- @Override
- public long getDuration() {
- return mDuration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
- t.setAlpha(sc, alpha);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
- pw.print(" to="); pw.print(mToAlpha);
- pw.print(" duration="); pw.println(mDuration);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ALPHA);
- proto.write(FROM, mFromAlpha);
- proto.write(TO, mToAlpha);
- proto.write(DURATION_MS, mDuration);
- proto.end(token);
- }
- }
+ abstract boolean updateDims(SurfaceControl.Transaction t);
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index df26b10..f51bf7f 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
-
/**
* Container for grouping WindowContainer below DisplayContent.
*
@@ -786,7 +785,7 @@
* DisplayArea that can be dimmed.
*/
static class Dimmable extends DisplayArea<DisplayArea> {
- private final Dimmer mDimmer = new Dimmer(this);
+ private final Dimmer mDimmer = Dimmer.create(this);
Dimmable(WindowManagerService wms, Type type, String name, int featureId) {
super(wms, type, name, featureId);
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930d..1fa7d2a 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -51,7 +51,8 @@
private final Rect mOldWindowCrop = new Rect();
InputConsumerImpl(WindowManagerService service, IBinder token, String name,
- InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) {
+ InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId,
+ SurfaceControl.Transaction t) {
mService = service;
mToken = token;
mName = name;
@@ -82,6 +83,7 @@
.setName("Input Consumer " + name)
.setCallsite("InputConsumerImpl")
.build();
+ mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
}
void linkToDeathRecipient() {
@@ -129,14 +131,12 @@
void show(SurfaceControl.Transaction t, WindowContainer w) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
}
void show(SurfaceControl.Transaction t, int layer) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, layer);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index af307ec3..5c0bc28 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -224,7 +224,7 @@
}
final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
- inputChannel, clientPid, clientUser, mDisplayId);
+ inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction);
switch (name) {
case INPUT_CONSUMER_WALLPAPER:
consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER;
@@ -675,11 +675,6 @@
w.getKeyInterceptionInfo());
if (w.mWinAnimator.hasSurface()) {
- // Update trusted overlay changes here because they are tied to input info. Input
- // changes can be updated even if surfaces aren't.
- inputWindowHandle.setTrustedOverlay(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl,
- w.isWindowTrustedOverlay());
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java
new file mode 100644
index 0000000..ccf956e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LegacyDimmer.java
@@ -0,0 +1,341 @@
+/*
+ * 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.server.wm;
+
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+
+public class LegacyDimmer extends Dimmer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ // This is in milliseconds.
+ private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+ DimState mDimState;
+ private WindowContainer mLastRequestedDimContainer;
+ private final SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+
+ private class DimAnimatable implements SurfaceAnimator.Animatable {
+ private SurfaceControl mDimLayer;
+
+ private DimAnimatable(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ }
+
+ @Override
+ public SurfaceControl.Transaction getSyncTransaction() {
+ return mHost.getSyncTransaction();
+ }
+
+ @Override
+ public SurfaceControl.Transaction getPendingTransaction() {
+ return mHost.getPendingTransaction();
+ }
+
+ @Override
+ public void commitPendingTransaction() {
+ mHost.commitPendingTransaction();
+ }
+
+ @Override
+ public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+ }
+
+ @Override
+ public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public SurfaceControl.Builder makeAnimationLeash() {
+ return mHost.makeAnimationLeash();
+ }
+
+ @Override
+ public SurfaceControl getAnimationLeashParent() {
+ return mHost.getSurfaceControl();
+ }
+
+ @Override
+ public SurfaceControl getSurfaceControl() {
+ return mDimLayer;
+ }
+
+ @Override
+ public SurfaceControl getParentSurfaceControl() {
+ return mHost.getSurfaceControl();
+ }
+
+ @Override
+ public int getSurfaceWidth() {
+ // This will determine the size of the leash created. This should be the size of the
+ // host and not the dim layer since the dim layer may get bigger during animation. If
+ // that occurs, the leash size cannot change so we need to ensure the leash is big
+ // enough that the dim layer can grow.
+ // This works because the mHost will be a Task which has the display bounds.
+ return mHost.getSurfaceWidth();
+ }
+
+ @Override
+ public int getSurfaceHeight() {
+ // See getSurfaceWidth() above for explanation.
+ return mHost.getSurfaceHeight();
+ }
+
+ void removeSurface() {
+ if (mDimLayer != null && mDimLayer.isValid()) {
+ getSyncTransaction().remove(mDimLayer);
+ }
+ mDimLayer = null;
+ }
+ }
+
+ @VisibleForTesting
+ class DimState {
+ /**
+ * The layer where property changes should be invoked on.
+ */
+ SurfaceControl mDimLayer;
+ boolean mDimming;
+ boolean mIsVisible;
+
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ /**
+ * Determines whether the dim layer should animate before destroying.
+ */
+ boolean mAnimateExit = true;
+
+ /**
+ * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
+ * details on Dim lifecycle.
+ */
+ boolean mDontReset;
+ SurfaceAnimator mSurfaceAnimator;
+
+ DimState(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ mDimming = true;
+ final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer);
+ mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> {
+ if (!mDimming) {
+ dimAnimatable.removeSurface();
+ }
+ }, mHost.mWmService);
+ }
+ }
+
+ @VisibleForTesting
+ interface SurfaceAnimatorStarter {
+ void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
+ AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type);
+ }
+
+ protected LegacyDimmer(WindowContainer host) {
+ this(host, SurfaceAnimator::startAnimation);
+ }
+
+ LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) {
+ super(host);
+ mSurfaceAnimatorStarter = surfaceAnimatorStarter;
+ }
+
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ try {
+ final SurfaceControl ctl = makeDimLayer();
+ mDimState = new DimState(ctl);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("Dimmer.makeDimLayer")
+ .build();
+ }
+
+ @Override
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimLayer : null;
+ }
+
+ @Override
+ void resetDimStates() {
+ if (mDimState == null) {
+ return;
+ }
+ if (!mDimState.mDontReset) {
+ mDimState.mDimming = false;
+ }
+ }
+
+ @Override
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ @Override
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
+
+ @Override
+ protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+
+ if (d == null) {
+ return;
+ }
+
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ SurfaceControl.Transaction t = mHost.getPendingTransaction();
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
+ t.setAlpha(d.mDimLayer, alpha);
+ t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
+
+ d.mDimming = true;
+ }
+
+ @Override
+ boolean updateDims(SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+
+ if (!mDimState.mDimming) {
+ if (!mDimState.mAnimateExit) {
+ if (mDimState.mDimLayer.isValid()) {
+ t.remove(mDimState.mDimLayer);
+ }
+ } else {
+ startDimExit(mLastRequestedDimContainer,
+ mDimState.mSurfaceAnimator, t);
+ }
+ mDimState = null;
+ return false;
+ } else {
+ final Rect bounds = mDimState.mDimBounds;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+ if (!mDimState.mIsVisible) {
+ mDimState.mIsVisible = true;
+ t.show(mDimState.mDimLayer);
+ // Skip enter animation while starting window is on top of its activity
+ final WindowState ws = mLastRequestedDimContainer.asWindowState();
+ if (ws == null || ws.mActivityRecord == null
+ || ws.mActivityRecord.mStartingData == null) {
+ startDimEnter(mLastRequestedDimContainer,
+ mDimState.mSurfaceAnimator, t);
+ }
+ }
+ return true;
+ }
+ }
+
+ private long getDimDuration(WindowContainer container) {
+ // Use the same duration as the animation on the WindowContainer
+ AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+ final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+ return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+ : animationAdapter.getDurationHint();
+ }
+
+ private void startDimEnter(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t) {
+ startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */);
+ }
+
+ private void startDimExit(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t) {
+ startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */);
+ }
+
+ private void startAnim(WindowContainer container, SurfaceAnimator animator,
+ SurfaceControl.Transaction t, float startAlpha, float endAlpha) {
+ mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter(
+ new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)),
+ mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */,
+ ANIMATION_TYPE_DIMMER);
+ }
+
+ private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+ private final long mDuration;
+ private final float mFromAlpha;
+ private final float mToAlpha;
+
+ AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) {
+ mFromAlpha = fromAlpha;
+ mToAlpha = toAlpha;
+ mDuration = duration;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ final float fraction = getFraction(currentPlayTime);
+ final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha;
+ t.setAlpha(sc, alpha);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("from="); pw.print(mFromAlpha);
+ pw.print(" to="); pw.print(mToAlpha);
+ pw.print(" duration="); pw.println(mDuration);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(ALPHA);
+ proto.write(FROM, mFromAlpha);
+ proto.write(TO, mToAlpha);
+ proto.write(DURATION_MS, mDuration);
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf6a1fe..7a442e7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2211,6 +2211,10 @@
mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
organizedTf);
}
+
+ if (taskDisplayArea.getFocusedRootTask() == rootTask) {
+ taskDisplayArea.clearPreferredTopFocusableRootTask();
+ }
} finally {
mService.continueWindowLayout();
try {
diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java
new file mode 100644
index 0000000..6ddbd2c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SmoothDimmer.java
@@ -0,0 +1,395 @@
+/*
+ * 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.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER;
+import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.AlphaAnimationSpecProto.FROM;
+import static com.android.server.wm.AlphaAnimationSpecProto.TO;
+import static com.android.server.wm.AnimationSpecProto.ALPHA;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+
+class SmoothDimmer extends Dimmer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+ private static final float EPSILON = 0.0001f;
+ // This is in milliseconds.
+ private static final int DEFAULT_DIM_ANIM_DURATION = 200;
+ DimState mDimState;
+ private WindowContainer mLastRequestedDimContainer;
+ private final AnimationAdapterFactory mAnimationAdapterFactory;
+
+ @VisibleForTesting
+ class DimState {
+ /**
+ * The layer where property changes should be invoked on.
+ */
+ SurfaceControl mDimLayer;
+ boolean mDimming;
+ boolean mIsVisible;
+
+ // TODO(b/64816140): Remove after confirming dimmer layer always matches its container.
+ final Rect mDimBounds = new Rect();
+
+ /**
+ * Determines whether the dim layer should animate before destroying.
+ */
+ boolean mAnimateExit = true;
+
+ /**
+ * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for
+ * details on Dim lifecycle.
+ */
+ boolean mDontReset;
+
+ Change mCurrentProperties;
+ Change mRequestedProperties;
+ private AnimationSpec mAlphaAnimationSpec;
+ private AnimationAdapter mLocalAnimationAdapter;
+
+ static class Change {
+ private float mAlpha = -1f;
+ private int mBlurRadius = -1;
+ private WindowContainer mDimmingContainer = null;
+ private int mRelativeLayer = -1;
+ private boolean mSkipAnimation = false;
+
+ Change() {}
+
+ Change(Change other) {
+ mAlpha = other.mAlpha;
+ mBlurRadius = other.mBlurRadius;
+ mDimmingContainer = other.mDimmingContainer;
+ mRelativeLayer = other.mRelativeLayer;
+ }
+
+ @Override
+ public String toString() {
+ return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container="
+ + mDimmingContainer + ", relativePosition=" + mRelativeLayer
+ + ", skipAnimation=" + mSkipAnimation;
+ }
+ }
+
+ DimState(SurfaceControl dimLayer) {
+ mDimLayer = dimLayer;
+ mDimming = true;
+ mCurrentProperties = new Change();
+ mRequestedProperties = new Change();
+ }
+
+ void setExitParameters(WindowContainer container) {
+ setRequestedParameters(container, -1, 0, 0);
+ }
+ // Sets a requested change without applying it immediately
+ void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha,
+ int blurRadius) {
+ mRequestedProperties.mDimmingContainer = container;
+ mRequestedProperties.mRelativeLayer = relativeLayer;
+ mRequestedProperties.mAlpha = alpha;
+ mRequestedProperties.mBlurRadius = blurRadius;
+ }
+
+ /**
+ * Commit the last changes we received. Called after
+ * {@link Change#setRequestedParameters(WindowContainer, int, float, int)}
+ */
+ void applyChanges(SurfaceControl.Transaction t) {
+ if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
+ Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+ + "does not have a surface");
+ return;
+ }
+ if (!mDimState.mIsVisible) {
+ mDimState.mIsVisible = true;
+ t.show(mDimState.mDimLayer);
+ }
+ t.setRelativeLayer(mDimLayer,
+ mRequestedProperties.mDimmingContainer.getSurfaceControl(),
+ mRequestedProperties.mRelativeLayer);
+
+ if (aspectChanged()) {
+ if (isAnimating()) {
+ mLocalAnimationAdapter.onAnimationCancelled(mDimLayer);
+ }
+ if (mRequestedProperties.mSkipAnimation
+ || (!dimmingContainerChanged() && mDimming)) {
+ // If the dimming container has not changed, then it is running its own
+ // animation, thus we can directly set the values we get requested, unless it's
+ // the exiting animation
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Dim %s skipping animation and directly setting alpha=%f, blur=%d",
+ mDimLayer, mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius);
+ t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+ t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+ mRequestedProperties.mSkipAnimation = false;
+ } else {
+ startAnimation(t);
+ }
+ }
+ mCurrentProperties = new Change(mRequestedProperties);
+ }
+
+ private void startAnimation(SurfaceControl.Transaction t) {
+ mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius);
+ mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
+ mHost.mWmService.mSurfaceAnimationRunner);
+
+ mLocalAnimationAdapter.startAnimation(mDimLayer, t,
+ ANIMATION_TYPE_DIMMER, (type, animator) -> {
+ t.setAlpha(mDimLayer, mRequestedProperties.mAlpha);
+ t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius);
+ if (mRequestedProperties.mAlpha == 0f && !mDimming) {
+ ProtoLog.d(WM_DEBUG_DIMMER,
+ "Removing dim surface %s on transaction %s", mDimLayer, t);
+ t.remove(mDimLayer);
+ }
+ mLocalAnimationAdapter = null;
+ mAlphaAnimationSpec = null;
+ });
+ }
+
+ private boolean isAnimating() {
+ return mAlphaAnimationSpec != null;
+ }
+
+ private boolean aspectChanged() {
+ return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON
+ || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius;
+ }
+
+ private boolean dimmingContainerChanged() {
+ return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer;
+ }
+
+ private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) {
+ final float startAlpha;
+ final int startBlur;
+ if (mAlphaAnimationSpec != null) {
+ startAlpha = mAlphaAnimationSpec.mCurrentAlpha;
+ startBlur = mAlphaAnimationSpec.mCurrentBlur;
+ } else {
+ startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+ startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+ }
+ long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+ * Math.abs(targetAlpha - startAlpha));
+
+ ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, "
+ + "alpha: %f -> %f, blur: %d -> %d",
+ mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha,
+ startBlur, targetBlur);
+ return new AnimationSpec(
+ new AnimationExtremes<>(startAlpha, targetAlpha),
+ new AnimationExtremes<>(startBlur, targetBlur),
+ duration
+ );
+ }
+ }
+
+ protected SmoothDimmer(WindowContainer host) {
+ this(host, new AnimationAdapterFactory());
+ }
+
+ @VisibleForTesting
+ SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) {
+ super(host);
+ mAnimationAdapterFactory = animationFactory;
+ }
+
+ private DimState obtainDimState(WindowContainer container) {
+ if (mDimState == null) {
+ try {
+ final SurfaceControl ctl = makeDimLayer();
+ mDimState = new DimState(ctl);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.w(TAG, "OutOfResourcesException creating dim surface");
+ }
+ }
+
+ mLastRequestedDimContainer = container;
+ return mDimState;
+ }
+
+ private SurfaceControl makeDimLayer() {
+ return mHost.makeChildSurface(null)
+ .setParent(mHost.getSurfaceControl())
+ .setColorLayer()
+ .setName("Dim Layer for - " + mHost.getName())
+ .setCallsite("Dimmer.makeDimLayer")
+ .build();
+ }
+
+ @Override
+ SurfaceControl getDimLayer() {
+ return mDimState != null ? mDimState.mDimLayer : null;
+ }
+
+ @Override
+ void resetDimStates() {
+ if (mDimState == null) {
+ return;
+ }
+ if (!mDimState.mDontReset) {
+ mDimState.mDimming = false;
+ }
+ }
+
+ @Override
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
+ }
+
+ @Override
+ void dontAnimateExit() {
+ if (mDimState != null) {
+ mDimState.mAnimateExit = false;
+ }
+ }
+
+ @Override
+ protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) {
+ final DimState d = obtainDimState(container);
+
+ mDimState.mRequestedProperties.mDimmingContainer = container;
+ mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius);
+ d.mDimming = true;
+ }
+
+ boolean updateDims(SurfaceControl.Transaction t) {
+ if (mDimState == null) {
+ return false;
+ }
+
+ if (!mDimState.mDimming) {
+ // No one is dimming anymore, fade out dim and remove
+ if (!mDimState.mAnimateExit) {
+ if (mDimState.mDimLayer.isValid()) {
+ t.remove(mDimState.mDimLayer);
+ }
+ } else {
+ mDimState.setExitParameters(
+ mDimState.mRequestedProperties.mDimmingContainer);
+ mDimState.applyChanges(t);
+ }
+ mDimState = null;
+ return false;
+ }
+ final Rect bounds = mDimState.mDimBounds;
+ // TODO: Once we use geometry from hierarchy this falls away.
+ t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top);
+ t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height());
+ // Skip enter animation while starting window is on top of its activity
+ final WindowState ws = mLastRequestedDimContainer.asWindowState();
+ if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null
+ && ws.mActivityRecord.mStartingData != null) {
+ mDimState.mRequestedProperties.mSkipAnimation = true;
+ }
+ mDimState.applyChanges(t);
+ return true;
+ }
+
+ private long getDimDuration(WindowContainer container) {
+ // Use the same duration as the animation on the WindowContainer
+ AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
+ final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
+ return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale)
+ : animationAdapter.getDurationHint();
+ }
+
+ private static class AnimationExtremes<T> {
+ final T mStartValue;
+ final T mFinishValue;
+
+ AnimationExtremes(T fromValue, T toValue) {
+ mStartValue = fromValue;
+ mFinishValue = toValue;
+ }
+ }
+
+ private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec {
+ private final long mDuration;
+ private final AnimationExtremes<Float> mAlpha;
+ private final AnimationExtremes<Integer> mBlur;
+
+ float mCurrentAlpha = 0;
+ int mCurrentBlur = 0;
+
+ AnimationSpec(AnimationExtremes<Float> alpha,
+ AnimationExtremes<Integer> blur, long duration) {
+ mAlpha = alpha;
+ mBlur = blur;
+ mDuration = duration;
+ }
+
+ @Override
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) {
+ final float fraction = getFraction(currentPlayTime);
+ mCurrentAlpha =
+ fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue;
+ mCurrentBlur =
+ (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue;
+ t.setAlpha(sc, mCurrentAlpha);
+ t.setBackgroundBlurRadius(sc, mCurrentBlur);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue);
+ pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue);
+ pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue);
+ pw.print(" to_blur="); pw.print(mBlur.mFinishValue);
+ pw.print(" duration="); pw.println(mDuration);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(ALPHA);
+ proto.write(FROM, mAlpha.mStartValue);
+ proto.write(TO, mAlpha.mFinishValue);
+ proto.write(DURATION_MS, mDuration);
+ proto.end(token);
+ }
+ }
+
+ static class AnimationAdapterFactory {
+
+ public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+ SurfaceAnimationRunner runner) {
+ return new LocalAnimationAdapter(alphaAnimationSpec, runner);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 408ea6e..c3de4d5 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -49,7 +49,8 @@
* {@link AnimationAdapter}. When the animation is done animating, our callback to finish the
* animation will be invoked, at which we reparent the children back to the original parent.
*/
-class SurfaceAnimator {
+@VisibleForTesting
+public class SurfaceAnimator {
private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM;
@@ -617,7 +618,8 @@
* Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the
* component that is running the animation when the animation is finished.
*/
- interface OnAnimationFinishedCallback {
+ @VisibleForTesting
+ public interface OnAnimationFinishedCallback {
void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dca0ed2..4922e90 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,6 +199,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.uri.NeededUriGrants;
+import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -488,10 +489,6 @@
private boolean mForceShowForAllUsers;
- /** When set, will force the task to report as invisible. */
- static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
- static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
- private int mForceHiddenFlags = 0;
private boolean mForceTranslucent = false;
// The display category name for this task.
@@ -2858,7 +2855,8 @@
}
/** Bounds of the task to be used for dimming, as well as touch related tests. */
- void getDimBounds(Rect out) {
+ @Override
+ void getDimBounds(@NonNull Rect out) {
if (isRootTask()) {
getBounds(out);
return;
@@ -3306,7 +3304,8 @@
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || isTranslucent(null)) {
+ if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+ || isTranslucent(null)) {
return super.getDimmer();
}
@@ -4492,20 +4491,13 @@
* Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
* @return Whether the force hidden state changed
*/
- boolean setForceHidden(int flags, boolean set) {
- int newFlags = mForceHiddenFlags;
- if (set) {
- newFlags |= flags;
- } else {
- newFlags &= ~flags;
- }
- if (mForceHiddenFlags == newFlags) {
- return false;
- }
-
+ @Override
+ boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
final boolean wasHidden = isForceHidden();
final boolean wasVisible = isVisible();
- mForceHiddenFlags = newFlags;
+ if (!super.setForceHidden(flags, set)) {
+ return false;
+ }
final boolean nowHidden = isForceHidden();
if (wasHidden != nowHidden) {
final String reason = "setForceHidden";
@@ -4536,11 +4528,6 @@
return super.isAlwaysOnTop();
}
- @Override
- protected boolean isForceHidden() {
- return mForceHiddenFlags != 0;
- }
-
boolean isForceHiddenForPinnedTask() {
return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825..906b3b5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,6 +103,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -209,7 +210,26 @@
*/
int mMinHeight;
- Dimmer mDimmer = new Dimmer(this);
+ Dimmer mDimmer = Flags.dimmerRefactor()
+ ? new SmoothDimmer(this) : new LegacyDimmer(this);
+
+ /** Apply the dim layer on the embedded TaskFragment. */
+ static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
+
+ /** Apply the dim layer on the parent Task for an embedded TaskFragment. */
+ static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1;
+
+ /**
+ * The type of dim layer area for an embedded TaskFragment.
+ */
+ @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = {
+ EMBEDDED_DIM_AREA_TASK_FRAGMENT,
+ EMBEDDED_DIM_AREA_PARENT_TASK,
+ })
+ @interface EmbeddedDimArea {}
+
+ @EmbeddedDimArea
+ private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;
/** This task fragment will be removed when the cleanup of its children are done. */
private boolean mIsRemovalRequested;
@@ -342,6 +362,19 @@
*/
private boolean mIsolatedNav;
+ /** When set, will force the task to report as invisible. */
+ static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
+ static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
+ static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2;
+
+ @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = {
+ FLAG_FORCE_HIDDEN_FOR_PINNED_TASK,
+ FLAG_FORCE_HIDDEN_FOR_TASK_ORG,
+ FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG,
+ }, flag = true)
+ @interface FlagForceHidden {}
+ protected int mForceHiddenFlags = 0;
+
final Point mLastSurfaceSize = new Point();
private final Rect mTmpBounds = new Rect();
@@ -825,7 +858,25 @@
* Returns whether this TaskFragment is currently forced to be hidden for any reason.
*/
protected boolean isForceHidden() {
- return false;
+ return mForceHiddenFlags != 0;
+ }
+
+ /**
+ * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
+ * @return Whether the force hidden state changed
+ */
+ boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
+ int newFlags = mForceHiddenFlags;
+ if (set) {
+ newFlags |= flags;
+ } else {
+ newFlags &= ~flags;
+ }
+ if (mForceHiddenFlags == newFlags) {
+ return false;
+ }
+ mForceHiddenFlags = newFlags;
+ return true;
}
protected boolean isForceTranslucent() {
@@ -969,7 +1020,7 @@
// A TaskFragment isn't translucent if it has at least one visible activity that occludes
// this TaskFragment.
return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
- starting) == null;
+ starting, true /* ignoringKeyguard */) == null;
}
/**
@@ -982,7 +1033,20 @@
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
+ true /* ignoringKeyguard */) == null;
+ }
+
+ /**
+ * Like {@link #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the
+ * windows rather than their visibility ignoring keyguard.
+ */
+ boolean isTranslucentAndVisible() {
+ if (!isAttached() || isForceHidden() || isForceTranslucent()) {
+ return true;
+ }
+ return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null,
+ false /* ignoringKeyguard */) == null;
}
ActivityRecord getTopNonFinishingActivity() {
@@ -2929,14 +2993,27 @@
@Override
Dimmer getDimmer() {
- // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
- if (asTask() == null) {
+ // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
+ if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {
return mDimmer;
}
return super.getDimmer();
}
+ /** Bounds to be used for dimming, as well as touch related tests. */
+ void getDimBounds(@NonNull Rect out) {
+ if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) {
+ out.set(getTask().getBounds());
+ } else {
+ out.set(getBounds());
+ }
+ }
+
+ void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) {
+ mEmbeddedDimArea = embeddedDimArea;
+ }
+
@Override
void prepareSurfaces() {
if (asTask() != null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 04164c2..ff766be 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -51,7 +51,6 @@
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
-import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -745,9 +744,9 @@
}
}
- boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+ boolean isSystemOrganizer(@NonNull IBinder organizerToken) {
final TaskFragmentOrganizerState state =
- mTaskFragmentOrganizerState.get(token.asBinder());
+ mTaskFragmentOrganizerState.get(organizerToken);
return state != null && state.mIsSystemOrganizer;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f3fb7c4..7d65c61 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -418,8 +418,8 @@
if (transientRoot == null) continue;
final WindowContainer<?> rootParent = transientRoot.getParent();
if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
- final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
- .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+ final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
+ .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
occludedCount++;
}
@@ -3321,8 +3321,8 @@
mFrozen.add(wc);
final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
changeInfo.mSnapshot = snapshotSurface;
- if (isDisplayRotation) {
- // This isn't cheap, so only do it for display rotations.
+ if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) {
+ // This isn't cheap, so only do it for rotation change.
changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
buffer, screenshotBuffer.getColorSpace());
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f339d24..9663f3a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2261,6 +2261,7 @@
}
}
+ final boolean wasTrustedOverlay = win.isWindowTrustedOverlay();
flagChanges = win.mAttrs.flags ^ attrs.flags;
privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
attrChanges = win.mAttrs.copyFrom(attrs);
@@ -2273,6 +2274,9 @@
if (layoutChanged && win.providesDisplayDecorInsets()) {
configChanged = displayPolicy.updateDecorInsetsInfo();
}
+ if (wasTrustedOverlay != win.isWindowTrustedOverlay()) {
+ win.updateTrustedOverlay();
+ }
if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
|| (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
win.mActivityRecord.checkKeyguardFlagsChanged();
@@ -5299,7 +5303,11 @@
public void displayReady() {
synchronized (mGlobalLock) {
if (mMaxUiWidth > 0) {
- mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));
+ mRoot.forAllDisplays(dc -> {
+ if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) {
+ dc.setMaxUiWidth(mMaxUiWidth);
+ }
+ });
}
applyForcedPropertiesForDefaultDisplay();
mAnimator.ready();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dd9a88f..5ed6caf 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -24,7 +24,9 @@
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
@@ -34,6 +36,8 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
+import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
+import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -61,6 +65,7 @@
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -821,6 +826,7 @@
return TRANSACT_EFFECTS_NONE;
}
+ int effects = TRANSACT_EFFECTS_NONE;
// When the TaskFragment is resized, we may want to create a change transition for it, for
// which we want to defer the surface update until we determine whether or not to start
// change transition.
@@ -843,7 +849,14 @@
c.getConfiguration().windowConfiguration.setBounds(absBounds);
taskFragment.setRelativeEmbeddedBounds(relBounds);
}
- final int effects = applyChanges(taskFragment, c);
+ if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
+ if (taskFragment.setForceHidden(
+ FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ effects |= applyChanges(taskFragment, c);
+
if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
taskFragment.initializeChangeTransition(mTmpBounds0);
}
@@ -1393,6 +1406,24 @@
taskFragment.setIsolatedNav(isolatedNav);
break;
}
+ case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
+ final Task task = taskFragment.getTask();
+ if (task != null) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(0, taskFragment);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ break;
+ }
+ case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
+ final Task task = taskFragment.getTask();
+ if (task != null) {
+ task.mChildren.remove(taskFragment);
+ task.mChildren.add(taskFragment);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ break;
+ }
}
return effects;
}
@@ -1420,6 +1451,18 @@
return false;
}
+ if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK
+ || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK)
+ && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ final Throwable exception = new SecurityException(
+ "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or "
+ + "OP_TYPE_REORDER_TO_TOP_OF_TASK."
+ );
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ opType, exception);
+ return false;
+ }
+
final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
return secondaryFragmentToken == null
|| validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
@@ -1920,6 +1963,11 @@
* For config change on {@link TaskFragment}, we only support the following operations:
* {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)},
* {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}.
+ *
+ * For a system organizer, we additionally support
+ * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and
+ * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See
+ * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)}
*/
private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func,
@Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change,
@@ -1938,31 +1986,49 @@
throw new SecurityException(msg);
}
- final int changeMask = change.getChangeMask();
- final int configSetMask = change.getConfigSetMask();
- final int windowSetMask = change.getWindowSetMask();
- if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0
- && change.getWindowingMode() >= 0) {
- // The change contains only setWindowingMode, which is allowed.
- return;
+ final int originalChangeMask = change.getChangeMask();
+ final int originalConfigSetMask = change.getConfigSetMask();
+ final int originalWindowSetMask = change.getWindowSetMask();
+
+ int changeMaskToBeChecked = originalChangeMask;
+ int configSetMaskToBeChecked = originalConfigSetMask;
+ int windowSetMaskToBeChecked = originalWindowSetMask;
+
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ // System organizer is allowed to update the hidden and focusable state.
+ // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here.
+ changeMaskToBeChecked &= ~CHANGE_HIDDEN;
+ changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;
}
- if (changeMask != CHANGE_RELATIVE_BOUNDS
- || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION
- || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) {
- // None of the change should be requested from a TaskFragment organizer except
- // setRelativeBounds and setWindowingMode.
- // For setRelativeBounds, we don't need to check whether it is outside of the Task
+
+ // setRelativeBounds is allowed.
+ if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0
+ && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
+ && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
+ // For setRelativeBounds, we don't need to check whether it is outside the Task
// bounds, because it is possible that the Task is also resizing, for which we don't
// want to throw an exception. The bounds will be adjusted in
// TaskFragment#translateRelativeBoundsToAbsoluteBounds.
- String msg = "Permission Denial: " + func + " from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " trying to apply changes of changeMask=" + changeMask
- + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask
- + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
+ changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS;
+ configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+ windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS;
}
+
+ if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0
+ && windowSetMaskToBeChecked == 0) {
+ // All the changes have been checked.
+ // Note that setWindowingMode is always allowed, so we don't need to check the mask.
+ return;
+ }
+
+ final String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " trying to apply changes of changeMask=" + originalChangeMask
+ + " configSetMask=" + originalConfigSetMask
+ + " windowSetMask=" + originalWindowSetMask
+ + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
@@ -2019,7 +2085,7 @@
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
taskFragment.setTaskFragmentOrganizer(organizerToken,
ownerActivity.getUid(), ownerActivity.info.processName,
- mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
+ mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder()));
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 726d4d7..7f36aec 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1189,7 +1189,20 @@
}
}
- public boolean isWindowTrustedOverlay() {
+ @Override
+ void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
+ super.setInitialSurfaceControlProperties(b);
+ if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
+ getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
+ }
+ }
+
+ void updateTrustedOverlay() {
+ mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
+ isWindowTrustedOverlay());
+ }
+
+ boolean isWindowTrustedOverlay() {
return InputMonitor.isTrustedOverlay(mAttrs.type)
|| ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
&& mSession.mCanAddInternalSystemWindow)
@@ -2756,12 +2769,7 @@
// bounds, as they would be used to display the dim layer.
final TaskFragment taskFragment = getTaskFragment();
if (taskFragment != null) {
- final Task task = taskFragment.asTask();
- if (task != null) {
- task.getDimBounds(mTmpRect);
- } else {
- mTmpRect.set(taskFragment.getBounds());
- }
+ taskFragment.getDimBounds(mTmpRect);
} else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
@@ -5205,9 +5213,6 @@
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
- if (surfaceTrustedOverlay()) {
- getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
- }
}
super.prepareSurfaces();
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e434f29..7d21dbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -569,6 +569,7 @@
&& asActivityRecord() != null && isVisible()) {
// Trigger an activity level rotation transition.
mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
+ mTransitionController.collectVisibleChange(this);
mTransitionController.setReady(this);
}
final int originalRotation = getWindowConfiguration().getRotation();
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
index b256f16..1844d30 100644
--- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -24,33 +24,33 @@
#include "utils/Log.h"
namespace android {
-static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds,
jfloatArray jthresholds) {
- if (juids == nullptr || jthresholds == nullptr) return;
+ if (jappIds == nullptr || jthresholds == nullptr) return;
- ScopedIntArrayRO uids(env, juids);
+ ScopedIntArrayRO appIds(env, jappIds);
ScopedFloatArrayRO thresholds(env, jthresholds);
- if (uids.size() != thresholds.size()) {
- ALOGE("uids size exceeds thresholds size!");
+ if (appIds.size() != thresholds.size()) {
+ ALOGE("appIds size exceeds thresholds size!");
return;
}
- std::vector<int32_t> uidVector;
+ std::vector<int32_t> appIdVector;
std::vector<float> thresholdVector;
- size_t size = uids.size();
- uidVector.reserve(size);
+ size_t size = appIds.size();
+ appIdVector.reserve(size);
thresholdVector.reserve(size);
for (int i = 0; i < size; i++) {
- uidVector.push_back(static_cast<int32_t>(uids[i]));
+ appIdVector.push_back(static_cast<int32_t>(appIds[i]));
thresholdVector.push_back(static_cast<float>(thresholds[i]));
}
- SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+ SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector);
}
-static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId,
jfloat threshold) {
- SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+ SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold);
}
static const JNINativeMethod gMethods[] = {
diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
index fc5a113..1e3cfd0 100644
--- a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
+++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp
@@ -15,106 +15,99 @@
*/
#include <android_runtime/AndroidRuntime.h>
-#include <nativehelper/JNIHelp.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
#include <jni.h>
+#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
-
-#include <utils/misc.h>
+#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <utils/Log.h>
-
-
-#include <inttypes.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
+#include <utils/misc.h>
namespace android {
- uint64_t get_block_device_size(int fd)
- {
- uint64_t size = 0;
- int ret;
+uint64_t get_block_device_size(int fd) {
+ uint64_t size = 0;
+ int ret;
- ret = ioctl(fd, BLKGETSIZE64, &size);
+ ret = ioctl(fd, BLKGETSIZE64, &size);
- if (ret)
- return 0;
+ if (ret) return 0;
- return size;
- }
+ return size;
+}
- int wipe_block_device(int fd)
- {
- uint64_t range[2];
- int ret;
- uint64_t len = get_block_device_size(fd);
+int wipe_block_device(int fd) {
+ uint64_t range[2];
+ int ret;
+ uint64_t len = get_block_device_size(fd);
+ range[0] = 0;
+ range[1] = len;
+
+ if (range[1] == 0) return 0;
+
+ ret = ioctl(fd, BLKSECDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
range[0] = 0;
range[1] = len;
-
- if (range[1] == 0)
- return 0;
-
- ret = ioctl(fd, BLKSECDISCARD, &range);
+ ret = ioctl(fd, BLKDISCARD, &range);
if (ret < 0) {
- ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
- range[0] = 0;
- range[1] = len;
- ret = ioctl(fd, BLKDISCARD, &range);
- if (ret < 0) {
- ALOGE("Discard failed: %s\n", strerror(errno));
- return -1;
- } else {
- ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
- return 0;
- }
-
+ ALOGE("Discard failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+ return 0;
}
-
- return ret;
}
- static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
- {
- ScopedUtfChars path(env, jpath);
- int fd = open(path.c_str(), O_RDONLY);
+ return ret;
+}
- if (fd < 0)
- return 0;
+static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env,
+ jclass,
+ jstring jpath) {
+ ScopedUtfChars path(env, jpath);
+ int fd = open(path.c_str(), O_RDONLY);
- const uint64_t size = get_block_device_size(fd);
+ if (fd < 0) return 0;
- close(fd);
+ const uint64_t size = get_block_device_size(fd);
- return size;
- }
+ close(fd);
- static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
- ScopedUtfChars path(env, jpath);
- int fd = open(path.c_str(), O_WRONLY);
+ return size;
+}
- if (fd < 0)
- return 0;
+static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass,
+ jstring jpath) {
+ ScopedUtfChars path(env, jpath);
+ int fd = open(path.c_str(), O_WRONLY);
- const int ret = wipe_block_device(fd);
+ if (fd < 0) return 0;
- close(fd);
+ const int ret = wipe_block_device(fd);
- return ret;
- }
+ close(fd);
- static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize},
- {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_pdb_PersistentDataBlockService_wipe},
- };
+ return ret;
+}
- int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env)
- {
- return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService",
- sMethods, NELEM(sMethods));
- }
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J",
+ (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize},
+ {"nativeWipe", "(Ljava/lang/String;)I",
+ (void *)com_android_server_pdb_PersistentDataBlockService_wipe},
+};
+
+int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) {
+ return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService",
+ sMethods, NELEM(sMethods));
+}
} /* namespace android */
\ No newline at end of file
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 5adcfba..4b5f8a7 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -19,4 +19,7 @@
defaults: ["platform_service_defaults"],
srcs: [":services.midi-sources"],
libs: ["services.core"],
+ static_libs: [
+ "aconfig_midi_flags_java_lib",
+ ],
}
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index a8902fc..2f47cc7 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,6 +16,8 @@
package com.android.server.midi;
+import static com.android.media.midi.flags.Flags.virtualUmp;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -1549,6 +1551,12 @@
return;
}
+ if (!virtualUmp()) {
+ Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
+ + ": virtual UMP flag not enabled");
+ return;
+ }
+
Bundle properties = null;
int numPorts = 0;
boolean isPrivate = false;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 2889c74..952cfc4 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -24,7 +24,9 @@
import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;
import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;
import static android.content.res.Resources.ID_NULL;
+
import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS;
+
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
@@ -1065,7 +1067,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1103,7 +1108,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));
assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi"));
@@ -1143,7 +1151,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
fail("Expected a PackageManagerException");
} catch (PackageManagerException expected) {
}
@@ -1179,7 +1190,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM));
@@ -1224,7 +1238,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1269,7 +1286,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1315,7 +1335,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
@@ -1358,7 +1381,10 @@
null /*usesStaticLibraries*/,
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID(),
+ false /*isPersistent*/,
+ 34 /*targetSdkVersion*/,
+ null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 55fde00..e71ea26 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.MANAGE_USB" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d021f1d..16d72e4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -117,7 +117,6 @@
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.input.InputManagerInternal;
import com.android.server.lights.LightsManager;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -312,7 +311,6 @@
@Mock SensorManager mSensorManager;
@Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
@Mock PackageManagerInternal mMockPackageManagerInternal;
- @Mock UserManagerInternal mMockUserManagerInternal;
@Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -336,8 +334,6 @@
VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
// TODO: b/287945043
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 9ac0062..32e2871 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -33,6 +33,7 @@
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.when;
import android.content.Context;
@@ -699,7 +700,7 @@
// Turn off.
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
- 0);
+ 0, null);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
assertThat(mListener.changedDisplays.size()).isEqualTo(1);
mListener.changedDisplays.clear();
@@ -1003,7 +1004,7 @@
// Turn on / initialize
assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1012,7 +1013,7 @@
// HDR time!
Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
- 0);
+ 0, null);
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
// Display state didn't change, no listeners should have happened
assertThat(mListener.changedDisplays.size()).isEqualTo(0);
@@ -1043,7 +1044,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1070,7 +1071,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1095,7 +1096,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1118,7 +1119,7 @@
// Turn on / initialize
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
- 0);
+ 0, null);
changeStateRunnable.run();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
mListener.changedDisplays.clear();
@@ -1145,9 +1146,9 @@
Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(
supportedState, 0, 0, mDisplayOffloadSession);
changeStateRunnable.run();
-
- verify(mDisplayOffloader).startOffload();
}
+
+ verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
new file mode 100644
index 0000000..d5a92cb
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/ConnectedDisplayUsbErrorsDetectorTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.display.notifications;
+
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_ENABLED;
+import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE;
+import static android.hardware.usb.DisplayPortAltModeInfo.LINK_TRAINING_STATUS_FAILURE;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.DisplayPortAltModeInfo;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.notifications.ConnectedDisplayUsbErrorsDetector.Injector;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ConnectedDisplayUsbErrorsDetector}
+ * Run: atest ConnectedDisplayUsbErrorsDetectorTest
+ */
+@SmallTest
+@RunWith(TestParameterInjector.class)
+public class ConnectedDisplayUsbErrorsDetectorTest {
+ @Mock
+ private Injector mMockedInjector;
+ @Mock
+ private UsbManager mMockedUsbManager;
+ @Mock
+ private DisplayManagerFlags mMockedFlags;
+ @Mock
+ private ConnectedDisplayUsbErrorsDetector.Listener mMockedListener;
+
+ /** Setup tests. */
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testNoErrorTypes(
+ @TestParameter final boolean isUsbManagerAvailable,
+ @TestParameter final boolean isUsbErrorsNotificationEnabled) {
+ // This is tested in #testErrorOnUsbCableNotCapableDp and #testErrorOnDpLinkTrainingFailure
+ assumeFalse(isUsbManagerAvailable && isUsbErrorsNotificationEnabled);
+ var detector = createErrorsDetector(isUsbManagerAvailable, isUsbErrorsNotificationEnabled);
+ // None of these should trigger an error now.
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+ verify(mMockedUsbManager, never()).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+ verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+ }
+
+ @Test
+ public void testErrorOnUsbCableNotCapableDp() {
+ var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+ /*isUsbErrorsNotificationEnabled=*/ true);
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnUsbCableNotCapableDp());
+ verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener).onCableNotCapableDisplayPort();
+ verify(mMockedListener, never()).onDisplayPortLinkTrainingFailure();
+ }
+
+ @Test
+ public void testErrorOnDpLinkTrainingFailure() {
+ var detector = createErrorsDetector(/*isUsbManagerAvailable=*/ true,
+ /*isUsbErrorsNotificationEnabled=*/ true);
+ detector.onDisplayPortAltModeInfoChanged("portId", createInfoOnDpLinkTrainingFailure());
+ verify(mMockedUsbManager).registerDisplayPortAltModeInfoListener(any(), any());
+ verify(mMockedListener, never()).onCableNotCapableDisplayPort();
+ verify(mMockedListener).onDisplayPortLinkTrainingFailure();
+ }
+
+ private ConnectedDisplayUsbErrorsDetector createErrorsDetector(
+ final boolean isUsbManagerAvailable,
+ final boolean isConnectedDisplayUsbErrorsNotificationEnabled) {
+ when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled())
+ .thenReturn(isConnectedDisplayUsbErrorsNotificationEnabled);
+ when(mMockedInjector.getUsbManager()).thenReturn(
+ (isUsbManagerAvailable) ? mMockedUsbManager : null);
+ var detector = new ConnectedDisplayUsbErrorsDetector(mMockedFlags,
+ ApplicationProvider.getApplicationContext(), mMockedInjector);
+ detector.registerListener(mMockedListener);
+ return detector;
+ }
+
+ private DisplayPortAltModeInfo createInfoOnUsbCableNotCapableDp() {
+ return new DisplayPortAltModeInfo(
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+ DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE, -1, false, 0);
+ }
+
+ private DisplayPortAltModeInfo createInfoOnDpLinkTrainingFailure() {
+ return new DisplayPortAltModeInfo(
+ DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED,
+ DISPLAYPORT_ALT_MODE_STATUS_ENABLED, -1, false,
+ LINK_TRAINING_STATUS_FAILURE);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
index d7c35ed..1d2034b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
@@ -83,14 +83,35 @@
}
@Test
+ public void testNotificationOnDisplayPortLinkTrainingFailure() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onDisplayPortLinkTrainingFailure();
+ assertExpectedNotification();
+ }
+
+ @Test
+ public void testNotificationOnCableNotCapableDisplayPort() {
+ var dnm = createDisplayNotificationManager(/*isNotificationManagerAvailable=*/ true,
+ /*isErrorHandlingEnabled=*/ true);
+ dnm.onCableNotCapableDisplayPort();
+ assertExpectedNotification();
+ }
+
+ @Test
public void testNoErrorNotification(
@TestParameter final boolean isNotificationManagerAvailable,
@TestParameter final boolean isErrorHandlingEnabled) {
- /* This case is tested by #testNotificationOnHotplugConnectionError */
+ /* This case is tested by #testNotificationOnHotplugConnectionError,
+ #testNotificationOnDisplayPortLinkTrainingFailure,
+ #testNotificationOnCableNotCapableDisplayPort */
assumeFalse(isNotificationManagerAvailable && isErrorHandlingEnabled);
var dnm = createDisplayNotificationManager(isNotificationManagerAvailable,
isErrorHandlingEnabled);
+ // None of these methods should trigger a notification now.
dnm.onHotplugConnectionError();
+ dnm.onDisplayPortLinkTrainingFailure();
+ dnm.onCableNotCapableDisplayPort();
verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any());
}
@@ -101,6 +122,8 @@
isErrorHandlingEnabled);
when(mMockedInjector.getNotificationManager()).thenReturn(
(isNotificationManagerAvailable) ? mMockedNotificationManager : null);
+ // Usb errors detector is tested in ConnectedDisplayUsbErrorsDetectorTest
+ when(mMockedInjector.getUsbErrorsDetector()).thenReturn(/* usbErrorsDetector= */ null);
final var displayNotificationManager = new DisplayNotificationManager(mMockedFlags,
ApplicationProvider.getApplicationContext(), mMockedInjector);
displayNotificationManager.onBootCompleted();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
index 1ce79a5..05ac5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -16,8 +16,6 @@
package com.android.server.display;
-import static android.os.Process.INVALID_UID;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -35,7 +33,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
import org.junit.Before;
import org.junit.Rule;
@@ -55,7 +53,10 @@
@Mock
private PackageManagerInternal mMockPackageManagerInternal;
@Mock
- private UserManagerInternal mMockUserManagerInternal;
+ private PackageStateInternal mMockPkgStateA;
+ @Mock
+ private PackageStateInternal mMockPkgStateB;
+
private SmallAreaDetectionController mSmallAreaDetectionController;
@@ -64,29 +65,18 @@
private static final String PKG_NOT_INSTALLED = "com.not.installed";
private static final float THRESHOLD_A = 0.05f;
private static final float THRESHOLD_B = 0.07f;
- private static final int USER_1 = 110;
- private static final int USER_2 = 111;
- private static final int UID_A_1 = 11011111;
- private static final int UID_A_2 = 11111111;
- private static final int UID_B_1 = 11022222;
- private static final int UID_B_2 = 11122222;
+ private static final int APP_ID_A = 11111;
+ private static final int APP_ID_B = 22222;
@Before
public void setup() {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
- when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
- when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
- when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
- when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
- when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
- when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
- INVALID_UID);
- when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
- INVALID_UID);
+ when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA);
+ when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB);
+ when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A);
+ when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B);
mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
new ContextWrapper(ApplicationProvider.getApplicationContext()),
@@ -99,9 +89,9 @@
final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
- final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_A, APP_ID_B};
+ final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
@@ -110,9 +100,9 @@
final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_B_1, UID_B_2};
- final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_B};
+ final float[] resultThresholdArray = {THRESHOLD_B};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
@@ -122,9 +112,9 @@
PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
mSmallAreaDetectionController.updateAllowlist(property);
- final int[] resultUidArray = {UID_A_1, UID_A_2};
- final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
- verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+ final int[] resultAppIdArray = {APP_ID_A};
+ final float[] resultThresholdArray = {THRESHOLD_A};
+ verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
eq(resultThresholdArray));
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
new file mode 100644
index 0000000..2003d04
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+import android.util.Xml;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.PowerStats;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.ParseException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AggregatedPowerStatsTest {
+ private static final int TEST_POWER_COMPONENT = 1077;
+ private static final int APP_1 = 27;
+ private static final int APP_2 = 42;
+
+ private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+ private PowerStats.Descriptor mPowerComponentDescriptor;
+
+ @Before
+ public void setup() throws ParseException {
+ mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+
+ mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3,
+ PersistableBundle.forPair("speed", "fast"));
+ }
+
+ @Test
+ public void aggregation() {
+ AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+ verifyAggregatedPowerStats(stats);
+ }
+
+ @Test
+ public void xmlPersistence() throws Exception {
+ AggregatedPowerStats stats = prepareAggregatePowerStats();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ serializer.setOutput(baos, "UTF-8");
+ stats.writeXml(serializer);
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8");
+ AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser,
+ mAggregatedPowerStatsConfig);
+
+ verifyAggregatedPowerStats(actualStats);
+ }
+
+ private AggregatedPowerStats prepareAggregatePowerStats() {
+ AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ stats.addClockUpdate(1000, 456);
+ stats.setDuration(789);
+
+ stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000);
+ stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_CACHED, 2000);
+ stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000);
+
+ PowerStats ps = new PowerStats(mPowerComponentDescriptor);
+ ps.stats[0] = 100;
+ ps.stats[1] = 987;
+
+ ps.uidStats.put(APP_1, new long[]{389, 0, 739});
+ ps.uidStats.put(APP_2, new long[]{278, 314, 628});
+
+ stats.addPowerStats(ps, 3000);
+
+ stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000);
+ stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000);
+
+ ps.stats[0] = 444;
+ ps.stats[1] = 0;
+
+ ps.uidStats.put(APP_1, new long[]{0, 0, 400});
+ ps.uidStats.put(APP_2, new long[]{100, 200, 300});
+
+ stats.addPowerStats(ps, 5000);
+
+ return stats;
+ }
+
+ private void verifyAggregatedPowerStats(AggregatedPowerStats stats) {
+ PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT)
+ .getPowerStatsDescriptor();
+ assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT);
+ assertThat(descriptor.name).isEqualTo("fan");
+ assertThat(descriptor.statsArrayLength).isEqualTo(2);
+ assertThat(descriptor.uidStatsArrayLength).isEqualTo(3);
+ assertThat(descriptor.extras.getString("speed")).isEqualTo("fast");
+
+ assertThat(getDeviceStats(stats,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON))
+ .isEqualTo(new long[]{322, 987});
+
+ assertThat(getDeviceStats(stats,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER))
+ .isEqualTo(new long[]{222, 0});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+ .isEqualTo(new long[]{259, 0, 492});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_CACHED))
+ .isEqualTo(new long[]{129, 0, 446});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_1,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_CACHED))
+ .isEqualTo(new long[]{0, 0, 200});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED))
+ .isEqualTo(new long[]{185, 209, 418});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND))
+ .isEqualTo(new long[]{142, 204, 359});
+
+ assertThat(getUidDeviceStats(stats,
+ APP_2,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND))
+ .isEqualTo(new long[]{50, 100, 150});
+ }
+
+ private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) {
+ long[] out = new long[states.length];
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states);
+ return out;
+ }
+
+ private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) {
+ long[] out = new long[states.length];
+ stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states);
+ return out;
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 5a2d2e3..663af5d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -42,6 +42,7 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.os.Clock;
import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.PowerProfile;
@@ -214,6 +215,7 @@
public class TestBatteryStatsImpl extends BatteryStatsImpl {
public TestBatteryStatsImpl(Context context) {
+ super(Clock.SYSTEM_CLOCK, null);
mPowerProfile = new PowerProfile(context, true /* forTest */);
SparseArray<int[]> cpusByPolicy = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 77124d0..abb3be7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -113,15 +113,15 @@
public void constrainedIteration() {
prepareHistory();
- // Initial time is 3000
+ // Initial time is 1000_000
assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0),
- 3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L);
- assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0),
- 1003_000L, 2003_000L, 2004_000L);
- assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L),
- 3_000L, 3_000L, 1003_000L);
+ 1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0),
+ 2000_000L, 3000_000L, 3001_000L);
+ assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L),
+ 1000_000L, 1000_000L, 2000_000L);
assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L),
- 1003_000L, 2003_000L);
+ 2000_000L);
}
private void prepareHistory() {
@@ -144,7 +144,7 @@
ArrayList<Long> actualTimestamps = new ArrayList<>();
while (iterator.hasNext()) {
BatteryStats.HistoryItem item = iterator.next();
- actualTimestamps.add(item.currentTime);
+ actualTimestamps.add(item.time);
}
assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index f22296a..1dd499c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -41,6 +41,7 @@
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import org.junit.Before;
@@ -69,6 +70,7 @@
private File mSystemDir;
private File mHistoryDir;
private final MockClock mClock = new MockClock();
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
private BatteryStatsHistory mHistory;
private BatteryStats.HistoryPrinter mHistoryPrinter;
@Mock
@@ -94,7 +96,7 @@
mClock.realtime = 123;
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- mStepDetailsCalculator, mClock, mTracer) {
+ mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {
@Override
public boolean readFileToParcel(Parcel out, AtomicFile file) {
mReadFiles.add(file.getBaseFile().getName());
@@ -210,7 +212,7 @@
mClock.realtime = 1000 * i;
fileList.add(mClock.realtime + ".bh");
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
verifyFileNames(mHistory, fileList);
verifyActiveFile(mHistory, mClock.realtime + ".bh");
@@ -218,7 +220,7 @@
// create file 32
mClock.realtime = 1000 * 32;
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
fileList.add("32000.bh");
fileList.remove(0);
@@ -229,7 +231,7 @@
// create file 33
mClock.realtime = 1000 * 33;
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
// verify file 1 is deleted
fileList.add("33000.bh");
@@ -240,7 +242,7 @@
// create a new BatteryStatsHistory object, it will pick up existing history files.
BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- null, mClock, mTracer);
+ null, mClock, mMonotonicClock, mTracer);
// verify constructor can pick up all files from file system.
verifyFileNames(history2, fileList);
verifyActiveFile(history2, "33000.bh");
@@ -262,7 +264,7 @@
// create file 1.
mClock.realtime = 2345678;
- history2.startNextFile();
+ history2.startNextFile(mClock.realtime);
createActiveFile(history2);
verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));
verifyActiveFile(history2, "2345678.bh");
@@ -336,14 +338,14 @@
mHistory.recordEvent(mClock.realtime, mClock.uptime,
BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42);
- mHistory.startNextFile(); // 1000.bh
+ mHistory.startNextFile(mClock.realtime); // 1000.bh
mClock.realtime = 2000;
mClock.uptime = 2000;
mHistory.recordEvent(mClock.realtime, mClock.uptime,
BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42);
- mHistory.startNextFile(); // 2000.bh
+ mHistory.startNextFile(mClock.realtime); // 2000.bh
mClock.realtime = 3000;
mClock.uptime = 3000;
@@ -351,7 +353,7 @@
HistoryItem.EVENT_ALARM, "alarm", 42);
// Flush accumulated history to disk
- mHistory.startNextFile();
+ mHistory.startNextFile(mClock.realtime);
}
private void verifyActiveFile(BatteryStatsHistory history, String file) {
@@ -518,7 +520,7 @@
// Keep the preserved part of history short - we only need to capture the very tail of
// history.
mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000,
- mStepDetailsCalculator, mClock, mTracer);
+ mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);
mHistory.forceRecordAllHistory();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 5df0acb..b1da1fc 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -40,6 +40,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
import org.junit.Rule;
@@ -64,6 +65,8 @@
new BatteryUsageStatsRule(12345, mHistoryDir)
.setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)
.setAveragePower(PowerProfile.POWER_AUDIO, 720.0);
+ private MockClock mMockClock = mStatsRule.getMockClock();
+
@Test
public void test_getBatteryUsageStats() {
BatteryStatsImpl batteryStats = prepareBatteryStats();
@@ -369,17 +372,23 @@
public void testAggregateBatteryStats() {
Context context = InstrumentationRegistry.getContext();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- mStatsRule.setCurrentTime(5 * MINUTE_IN_MS);
+ MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+
+ setTime(5 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
- BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context,
- batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
- new TestHandler(), Integer.MAX_VALUE);
- batteryUsageStatsStore.onSystemReady();
+
+ PowerStatsStore powerStatsStore = new PowerStatsStore(
+ new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
+ new TestHandler(), null);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
- batteryStats, batteryUsageStatsStore);
+ batteryStats, powerStatsStore);
+
+ batteryStats.setBatteryResetListener(reason ->
+ powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(),
+ provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -389,7 +398,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(25 * MINUTE_IN_MS);
+ setTime(25 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -402,7 +411,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(55 * MINUTE_IN_MS);
+ setTime(55 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -416,7 +425,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(75 * MINUTE_IN_MS);
+ setTime(75 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
@@ -430,7 +439,7 @@
batteryStats.noteFlashlightOffLocked(APP_UID,
90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);
}
- mStatsRule.setCurrentTime(95 * MINUTE_IN_MS);
+ setTime(95 * MINUTE_IN_MS);
// Include the first and the second snapshot, but not the third or current
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
@@ -457,29 +466,41 @@
.of(180.0);
}
+ private void setTime(long timeMs) {
+ mMockClock.currentTime = timeMs;
+ mMockClock.realtime = timeMs;
+ }
+
@Test
public void testAggregateBatteryStats_incompatibleSnapshot() {
Context context = InstrumentationRegistry.getContext();
MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
- BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+ PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
- when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
- .thenReturn(new long[]{1000, 2000});
-
- when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+ PowerStatsSpan span0 = new PowerStatsSpan(0);
+ span0.addTimeFrame(0, 1000, 1234);
+ span0.addSection(new BatteryUsageStatsSection(
new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
- .setStatsDuration(1234).build());
+ .setStatsDuration(1234).build()));
- // Add a snapshot, with a different set of custom power components. It should
- // be skipped by the aggregation.
- when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+ PowerStatsSpan span1 = new PowerStatsSpan(1);
+ span1.addTimeFrame(0, 2000, 4321);
+ span1.addSection(new BatteryUsageStatsSection(
new BatteryUsageStats.Builder(new String[]{"different"})
- .setStatsDuration(4321).build());
+ .setStatsDuration(4321).build()));
+
+ when(powerStatsStore.getTableOfContents()).thenReturn(
+ List.of(span0.getMetadata(), span1.getMetadata()));
+
+ when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE))
+ .thenReturn(span0);
+ when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
+ .thenReturn(span1);
BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
- batteryStats, batteryUsageStatsStore);
+ batteryStats, powerStatsStore);
BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
.aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 93cbea6..3579fce 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -88,6 +88,10 @@
mBatteryStats.onSystemReady();
}
+ public MockClock getMockClock() {
+ return mMockClock;
+ }
+
public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
mPowerProfile.forceInitForTesting(mContext, xmlId);
return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
deleted file mode 100644
index b846e3a..0000000
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-
-import android.content.Context;
-import android.os.BatteryManager;
-import android.os.BatteryUsageStats;
-import android.os.BatteryUsageStatsQuery;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Xml;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.PowerProfile;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-@RunWith(AndroidJUnit4.class)
-@SuppressWarnings("GuardedBy")
-public class BatteryUsageStatsStoreTest {
- private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
-
- private final MockClock mMockClock = new MockClock();
- private MockBatteryStatsImpl mBatteryStats;
- private BatteryUsageStatsStore mBatteryUsageStatsStore;
- private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
- private File mStoreDirectory;
-
- @Before
- public void setup() {
- mMockClock.currentTime = 123;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock);
- mBatteryStats.setNoAutoReset(true);
- mBatteryStats.setPowerProfile(mock(PowerProfile.class));
- mBatteryStats.onSystemReady();
-
- Context context = InstrumentationRegistry.getContext();
-
- mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest");
- clearDirectory(mStoreDirectory);
-
- mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats,
- mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- mBatteryUsageStatsStore.onSystemReady();
-
- mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
- }
-
- @Test
- public void testStoreSnapshot() {
- mMockClock.currentTime = 1_600_000;
- mMockClock.realtime = 1000;
- mMockClock.uptime = 1000;
-
- prepareBatteryStats();
-
- mMockClock.realtime = 1_000_000;
- mMockClock.uptime = 1_000_000;
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- assertThat(timestamps).hasLength(1);
- assertThat(timestamps[0]).isEqualTo(1_600_000);
-
- final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats(
- 1_600_000);
- assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123);
- assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000);
- assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000);
- assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5);
- assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000);
- assertThat(batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower())
- .isEqualTo(600); // (3_600_000 - 3_000_000) / 1000
- }
-
- @Test
- public void testGarbageCollectOldSnapshots() throws Exception {
- prepareBatteryStats();
-
- mMockClock.realtime = 10_000_000;
- mMockClock.uptime = 10_000_000;
- mMockClock.currentTime = 10_000_000;
-
- final int snapshotFileSize = getSnapshotFileSize();
- final int numberOfSnapshots =
- (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize);
- for (int i = 0; i < numberOfSnapshots + 2; i++) {
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
-
- mMockClock.realtime += 10_000_000;
- mMockClock.uptime += 10_000_000;
- mMockClock.currentTime += 10_000_000;
- prepareBatteryStats();
- }
-
- final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
- Arrays.sort(timestamps);
- assertThat(timestamps).hasLength(numberOfSnapshots);
- // Two snapshots (10_000_000 and 20_000_000) should have been discarded
- assertThat(timestamps[0]).isEqualTo(30_000_000);
- assertThat(getDirectorySize(mStoreDirectory))
- .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
- }
-
- @Test
- public void testRemoveAllSnapshots() throws Exception {
- prepareBatteryStats();
-
- for (int i = 0; i < 3; i++) {
- mMockClock.realtime += 10_000_000;
- mMockClock.uptime += 10_000_000;
- mMockClock.currentTime += 10_000_000;
- prepareBatteryStats();
-
- mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
- }
-
- assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
-
- mBatteryUsageStatsStore.removeAllSnapshots();
-
- assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
- }
-
- @Test
- public void testSavingStatsdAtomPullTimestamp() {
- mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
- assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
- .isEqualTo(1234);
- mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
- assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
- .isEqualTo(5478);
- }
-
- private void prepareBatteryStats() {
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
- /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
- mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime);
- mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
- /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0,
- mMockClock.realtime + 500_000, mMockClock.uptime + 500_000,
- mMockClock.currentTime + 500_000);
- }
-
- private void clearDirectory(File dir) {
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- clearDirectory(child);
- }
- child.delete();
- }
- }
- }
-
- private long getDirectorySize(File dir) {
- long size = 0;
- if (dir.exists()) {
- for (File child : dir.listFiles()) {
- if (child.isDirectory()) {
- size += getDirectorySize(child);
- } else {
- size += child.length();
- }
- }
- }
- return size;
- }
-
- private int getSnapshotFileSize() throws IOException {
- BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats(
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includePowerModels()
- .build());
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- TypedXmlSerializer serializer = Xml.newBinarySerializer();
- serializer.setOutput(out, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- stats.writeXml(serializer);
- serializer.endDocument();
- return out.toByteArray().length;
- }
-
- private static class TestHandler extends Handler {
- TestHandler() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- msg.getCallback().run();
- return true;
- }
- }
-}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 4ecee9f..30a73181 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -122,7 +122,7 @@
// 4 * 10 = 40 bits needed to represent the composite state
MultiStateStats.States[] states = new MultiStateStats.States[10];
for (int i = 0; i < states.length; i++) {
- states[i] = new MultiStateStats.States(true, labels);
+ states[i] = new MultiStateStats.States("foo", true, labels);
}
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> new MultiStateStats.Factory(DIMENSION_COUNT, states));
@@ -191,8 +191,8 @@
private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,
boolean trackProcState, boolean trackScreenState) {
return new MultiStateStats.Factory(DIMENSION_COUNT,
- new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"),
- new MultiStateStats.States(trackProcState,
+ new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"),
+ new MultiStateStats.States("ps", trackProcState,
BatteryConsumer.processStateToString(
BatteryConsumer.PROCESS_STATE_UNSPECIFIED),
BatteryConsumer.processStateToString(
@@ -203,7 +203,7 @@
BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),
BatteryConsumer.processStateToString(
BatteryConsumer.PROCESS_STATE_CACHED)),
- new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in"));
+ new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));
}
private FactorySubject assertThatCpuPerformanceStatsFactory(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 47de443..b52fc8a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,11 +24,14 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.PersistableBundle;
+import android.text.format.DateFormat;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerStats;
import org.junit.Before;
@@ -36,16 +39,19 @@
import org.junit.runner.RunWith;
import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.TimeZone;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PowerStatsAggregatorTest {
private static final int TEST_POWER_COMPONENT = 77;
private static final int TEST_UID = 42;
+ private static final long START_TIME = 1234;
private final MockClock mClock = new MockClock();
- private long mStartTime;
+ private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);
private BatteryStatsHistory mHistory;
private PowerStatsAggregator mAggregator;
private int mAggregatedStatsCount;
@@ -53,25 +59,25 @@
@Before
public void setup() throws ParseException {
mHistory = new BatteryStatsHistory(32, 1024,
- mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock);
- mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm")
- .parse("2008-09-23 08:00").getTime();
- mClock.currentTime = mStartTime;
+ mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
+ mMonotonicClock);
- PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory);
- builder.trackPowerComponent(TEST_POWER_COMPONENT)
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(TEST_POWER_COMPONENT)
.trackDeviceStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN)
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
.trackUidStates(
- PowerStatsAggregator.STATE_POWER,
- PowerStatsAggregator.STATE_SCREEN,
- PowerStatsAggregator.STATE_PROCESS_STATE);
- mAggregator = builder.build();
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+ mAggregator = new PowerStatsAggregator(config, mHistory);
}
@Test
public void stateUpdates() {
+ mClock.currentTime = 1222156800000L; // An important date in world history
+
mHistory.forceRecordAllHistory();
mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);
mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime,
@@ -98,15 +104,32 @@
mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- advance(3000);
+ advance(1000);
+
+ mClock.currentTime += 60 * 60 * 1000; // one hour
+ mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime);
+
+ advance(2000);
powerStats.stats = new long[]{20000};
powerStats.uidStats.put(TEST_UID, new long[]{4444});
mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
- mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ mAggregator.aggregatePowerStats(0, 0, stats -> {
assertThat(mAggregatedStatsCount++).isEqualTo(0);
- assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME);
+
+ List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+ assertThat(clockUpdates).hasSize(2);
+
+ AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0);
+ assertThat(clockUpdate0.monotonicTime).isEqualTo(1234);
+ assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00");
+
+ AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1);
+ assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000);
+ assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03");
+
assertThat(stats.getDuration()).isEqualTo(5000);
long[] values = new long[1];
@@ -115,40 +138,47 @@
TEST_POWER_COMPONENT);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{10000});
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER}))
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}))
.isTrue();
assertThat(values).isEqualTo(new long[]{20000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1234});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1111});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_OTHER,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,
BatteryConsumer.PROCESS_STATE_BACKGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{3333});
});
}
+ @NonNull
+ private static CharSequence formatDateTime(long timeInMillis) {
+ Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ cal.setTimeInMillis(timeInMillis);
+ return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+ }
+
@Test
public void incompatiblePowerStats() {
mHistory.forceRecordAllHistory();
@@ -181,39 +211,39 @@
mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
- mAggregator.aggregateBatteryStats(0, 0, stats -> {
+ mAggregator.aggregatePowerStats(0, 0, stats -> {
long[] values = new long[1];
PowerComponentAggregatedPowerStats powerComponentStats =
stats.getPowerComponentStats(TEST_POWER_COMPONENT);
if (mAggregatedStatsCount == 0) {
- assertThat(stats.getStartTime()).isEqualTo(mStartTime);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME);
assertThat(stats.getDuration()).isEqualTo(2000);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{10000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_OTHER,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_OTHER,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{1234});
} else if (mAggregatedStatsCount == 1) {
- assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000);
+ assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000);
assertThat(stats.getDuration()).isEqualTo(1000);
assertThat(powerComponentStats.getDeviceStats(values, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_ON}))
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON}))
.isTrue();
assertThat(values).isEqualTo(new long[]{20000});
assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{
- PowerStatsAggregator.POWER_STATE_BATTERY,
- PowerStatsAggregator.SCREEN_STATE_ON,
+ AggregatedPowerStatsConfig.POWER_STATE_BATTERY,
+ AggregatedPowerStatsConfig.SCREEN_STATE_ON,
BatteryConsumer.PROCESS_STATE_FOREGROUND}))
.isTrue();
assertThat(values).isEqualTo(new long[]{4444});
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
new file mode 100644
index 0000000..0e58787
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class PowerStatsSchedulerTest {
+ private PowerStatsStore mPowerStatsStore;
+ private Handler mHandler;
+ private MockClock mClock = new MockClock();
+ private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+ private MockBatteryStatsImpl mBatteryStats;
+ private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+ private PowerStatsScheduler mPowerStatsScheduler;
+ private PowerProfile mPowerProfile;
+ private PowerStatsAggregator mPowerStatsAggregator;
+ private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+ @Before
+ public void setup() {
+ final Context context = InstrumentationRegistry.getContext();
+
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+ mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+ mClock.realtime = 7654321;
+
+ HandlerThread bgThread = new HandlerThread("bg thread");
+ bgThread.start();
+ File systemDir = context.getCacheDir();
+ mHandler = new Handler(bgThread.getLooper());
+ mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
+ mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
+ mPowerProfile = mock(PowerProfile.class);
+ when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
+ mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+ mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats);
+ mPowerStatsAggregator = mock(PowerStatsAggregator.class);
+ mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
+ TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
+ mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void storeAggregatePowerStats() {
+ mPowerStatsStore.reset();
+
+ assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+ mPowerStatsStore.storeAggregatedPowerStats(
+ createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
+ 123));
+
+ long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
+ mClock.realtime += delayBeforeAggregating;
+ mClock.currentTime += delayBeforeAggregating;
+
+ doAnswer(invocation -> {
+ // The first span is longer than 30 min, because the end time is being aligned with
+ // the wall clock. Subsequent spans should be precisely 30 minutes.
+ long startTime = invocation.getArgument(0);
+ long endTime = invocation.getArgument(1);
+ Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+ long startTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+ long endTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+ assertThat(startTime).isEqualTo(7654321 + 123);
+ assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
+ assertThat(Instant.ofEpochMilli(endTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+
+ consumer.accept(
+ createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+ return null;
+ }).doAnswer(invocation -> {
+ long startTime = invocation.getArgument(0);
+ long endTime = invocation.getArgument(1);
+ Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
+
+ long startTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+ long endTimeWallClock =
+ mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+ assertThat(Instant.ofEpochMilli(startTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+ assertThat(Instant.ofEpochMilli(endTimeWallClock))
+ .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
+
+ consumer.accept(
+ createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+ return null;
+ }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
+ any(Consumer.class));
+
+ mPowerStatsScheduler.schedulePowerStatsAggregation();
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+
+ verify(mPowerStatsAggregator, times(2))
+ .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
+
+ List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+ assertThat(contents).hasSize(3);
+ // Skip the first entry, which was placed in the store at the beginning of this test
+ PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
+ PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
+ assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+ assertThat(timeFrame2.startMonotonicTime)
+ .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
+ assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
+ .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+ assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
+ }
+
+ private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
+ long duration) {
+ AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ stats.addClockUpdate(monotonicTime, currentTime);
+ stats.setDuration(duration);
+ return stats;
+ }
+
+ @Test
+ public void storeBatteryUsageStatsOnReset() {
+ mBatteryStats.forceRecordAllHistory();
+ synchronized (mBatteryStats) {
+ mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true,
+ BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0);
+ }
+
+ mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false);
+
+ assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+ mPowerStatsScheduler.start(true);
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime);
+ }
+
+ mClock.realtime += 60000;
+ mClock.currentTime += 60000;
+
+ synchronized (mBatteryStats) {
+ mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime);
+ }
+
+ mClock.realtime += 60000;
+ mClock.currentTime += 60000;
+
+ // Battery stats reset should have the side-effect of saving accumulated battery usage stats
+ synchronized (mBatteryStats) {
+ mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
+ }
+
+ // Await completion
+ ConditionVariable done = new ConditionVariable();
+ mHandler.post(done::open);
+ done.block();
+
+ List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+ assertThat(contents).hasSize(1);
+
+ PowerStatsSpan.Metadata metadata = contents.get(0);
+
+ PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ BatteryUsageStatsSection.TYPE);
+ assertThat(span).isNotNull();
+
+ List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames();
+ assertThat(timeFrames).hasSize(1);
+ assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321);
+ assertThat(timeFrames.get(0).duration).isEqualTo(120000);
+
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ assertThat(sections).hasSize(1);
+
+ PowerStatsSpan.Section section = sections.get(0);
+ assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE);
+ BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats();
+ assertThat(bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
+ .isEqualTo(60000);
+ }
+
+ @Test
+ public void alignToWallClock() {
+ // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT1M30S").toMillis());
+
+ // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT2M45S").toMillis());
+
+ // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1),
+ 123 + TimeUnit.HOURS.toMillis(2),
+ Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT15S").toMillis());
+
+ // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec -
+ // rounded to the next 3 hours
+ assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3),
+ 123 + TimeUnit.HOURS.toMillis(9),
+ Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo(
+ 123 + Duration.parse("PT1H46M30S").toMillis());
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
new file mode 100644
index 0000000..d3628b5
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
+public class PowerStatsStoreTest {
+ private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
+
+ private PowerStatsStore mPowerStatsStore;
+ private File mStoreDirectory;
+
+ @Before
+ public void setup() {
+ Context context = InstrumentationRegistry.getContext();
+
+ mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest");
+ clearDirectory(mStoreDirectory);
+
+ mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
+ MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES,
+ new TestHandler(),
+ (sectionType, parser) -> {
+ if (sectionType.equals(TestSection.TYPE)) {
+ return TestSection.readXml(parser);
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void garbageCollectOldSpans() throws Exception {
+ int spanSize = 500;
+ final int numberOfSnaps =
+ (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize);
+ for (int i = 0; i < numberOfSnaps + 2; i++) {
+ PowerStatsSpan span = new PowerStatsSpan(i);
+ span.addSection(new TestSection(i, spanSize));
+ mPowerStatsStore.storePowerStatsSpan(span);
+ }
+
+ assertThat(getDirectorySize(mStoreDirectory))
+ .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES);
+
+ List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents();
+ assertThat(toc.size()).isLessThan(numberOfSnaps);
+ int minPreservedSpanId = numberOfSnaps - toc.size();
+ for (PowerStatsSpan.Metadata metadata : toc) {
+ assertThat(metadata.getId()).isAtLeast(minPreservedSpanId);
+ }
+ }
+
+ @Test
+ public void reset() throws Exception {
+ for (int i = 0; i < 3; i++) {
+ PowerStatsSpan span = new PowerStatsSpan(i);
+ span.addSection(new TestSection(i, 42));
+ mPowerStatsStore.storePowerStatsSpan(span);
+ }
+
+ assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);
+
+ mPowerStatsStore.reset();
+
+ assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0);
+ }
+
+ private void clearDirectory(File dir) {
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ clearDirectory(child);
+ }
+ child.delete();
+ }
+ }
+ }
+
+ private long getDirectorySize(File dir) {
+ long size = 0;
+ if (dir.exists()) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ size += getDirectorySize(child);
+ } else {
+ size += child.length();
+ }
+ }
+ }
+ return size;
+ }
+
+ private static class TestSection extends PowerStatsSpan.Section {
+ public static final String TYPE = "much-text";
+
+ private final int mSize;
+ private final int mValue;
+
+ TestSection(int value, int size) {
+ super(TYPE);
+ mSize = size;
+ mValue = value;
+ }
+
+ @Override
+ void write(TypedXmlSerializer serializer) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mSize; i++) {
+ sb.append("X");
+ }
+ serializer.startTag(null, "much-text");
+ serializer.attributeInt(null, "value", mValue);
+ serializer.text(sb.toString());
+ serializer.endTag(null, "much-text");
+ }
+
+ public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException {
+ TestSection section = null;
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT
+ && !(eventType == XmlPullParser.END_TAG
+ && parser.getName().equals("much-text"))) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) {
+ section = new TestSection(parser.getAttributeInt(null, "value"), 0);
+ }
+ }
+ return section;
+ }
+ }
+
+ private static class TestHandler extends Handler {
+ TestHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ msg.getCallback().run();
+ return true;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 988cd81..feb6bd9 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -34,6 +36,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -49,8 +52,9 @@
final Context context = InstrumentationRegistry.getContext();
mBgThread = new HandlerThread("bg thread");
mBgThread.start();
- mBatteryStatsService = new BatteryStatsService(context,
- context.getCacheDir(), new Handler(mBgThread.getLooper()));
+ File systemDir = context.getCacheDir();
+ Handler handler = new Handler(mBgThread.getLooper());
+ mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);
}
@After
@@ -121,4 +125,14 @@
waitThread.join(1000);
}
}
+
+ @Test
+ public void testSavingStatsdAtomPullTimestamp() {
+ mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234);
+ assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+ .isEqualTo(1234);
+ mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478);
+ assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp())
+ .isEqualTo(5478);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 12d6161..5cc84b1 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -59,6 +59,7 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Collections;
import java.util.List;
/**
@@ -103,6 +104,10 @@
private boolean mDevCfgEnableContentProtectionReceiver;
+ private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a"));
+
+ private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+
private int mContentProtectionBlocklistManagersCreated;
private int mContentProtectionServiceInfosCreated;
@@ -374,7 +379,21 @@
}
@Test
- public void isContentProtectionReceiverEnabled_withoutManagers() {
+ public void isContentProtectionReceiverEnabled_true() {
+ when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_false_withoutManagers() {
boolean actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
USER_ID, PACKAGE_NAME);
@@ -385,7 +404,7 @@
}
@Test
- public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+ public void isContentProtectionReceiverEnabled_false_disabledWithFlag() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
@@ -400,6 +419,22 @@
}
@Test
+ public void isContentProtectionReceiverEnabled_false_emptyGroups() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mDevCfgContentProtectionRequiredGroups = Collections.emptyList();
+ mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
public void onLoginDetected_disabledAfterConstructor() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -525,6 +560,10 @@
super(sContext);
this.mDevCfgEnableContentProtectionReceiver =
ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ this.mDevCfgContentProtectionRequiredGroups =
+ ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups;
+ this.mDevCfgContentProtectionOptionalGroups =
+ ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
index 75d71da..06f117b 100644
--- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
@@ -38,9 +38,10 @@
public class BluetoothRouteControllerTest {
private final BluetoothRouteController.BluetoothRoutesUpdatedListener
- mBluetoothRoutesUpdatedListener = routes -> {
- // Empty on purpose.
- };
+ mBluetoothRoutesUpdatedListener =
+ () -> {
+ // Empty on purpose.
+ };
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index ec4b8a8..14b121d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -38,7 +38,7 @@
public class DeviceRouteControllerTest {
private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener =
- deviceRoute -> {
+ () -> {
// Empty on purpose.
};
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
new file mode 100644
index 0000000..07cdf4d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Tests for the {@link MediaProjectionSessionIdGenerator} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionSessionIdGeneratorTest {
+
+ private static final String TEST_PREFS_FILE = "media-projection-session-id-test";
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+ private final SharedPreferences mSharedPreferences = createSharePreferences();
+ private final MediaProjectionSessionIdGenerator mGenerator =
+ createGenerator(mSharedPreferences);
+
+ @Before
+ public void setUp() {
+ mSharedPreferences.edit().clear().commit();
+ }
+
+ @After
+ public void tearDown() {
+ mSharedPreferences.edit().clear().commit();
+ mSharedPreferencesFile.delete();
+ }
+
+ @Test
+ public void getCurrentSessionId_byDefault_returns0() {
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ }
+
+ @Test
+ public void getCurrentSessionId_multipleTimes_returnsSameValue() {
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+ }
+
+ @Test
+ public void createAndGetNewSessionId_returnsIncrementedId() {
+ int previousValue = mGenerator.getCurrentSessionId();
+
+ int newValue = mGenerator.createAndGetNewSessionId();
+
+ assertThat(newValue).isEqualTo(previousValue + 1);
+ }
+
+ @Test
+ public void createAndGetNewSessionId_persistsNewValue() {
+ int newValue = mGenerator.createAndGetNewSessionId();
+
+ MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences());
+
+ assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue);
+ }
+
+ private SharedPreferences createSharePreferences() {
+ return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+ }
+
+ private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) {
+ return new MediaProjectionSessionIdGenerator(sharedPreferences);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 8e7ba70..dd7dec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -130,12 +130,22 @@
// verify if back animation would start.
assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
- // reset drawing status
+ // reset drawing status to test translucent activity
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
- topTask.forAllWindows(w -> {
- makeWindowVisibleAndDrawn(w);
- }, true);
+ final ActivityRecord topActivity = topTask.getTopMostActivity();
+ makeWindowVisibleAndDrawn(topActivity.findMainWindow());
+ // simulate translucent
+ topActivity.setOccludesParent(false);
+ backNavigationInfo = startBackNavigation();
+ assertThat(typeToString(backNavigationInfo.getType()))
+ .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+ // reset drawing status to test keyguard occludes
+ topActivity.setOccludesParent(true);
+ backNavigationInfo.onBackNavigationFinished(false);
+ mBackNavigationController.clearBackAnimations();
+ makeWindowVisibleAndDrawn(topActivity.findMainWindow());
setupKeyguardOccluded();
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -201,9 +211,7 @@
// reset drawing status
backNavigationInfo.onBackNavigationFinished(false);
mBackNavigationController.clearBackAnimations();
- testCase.recordFront.forAllWindows(w -> {
- makeWindowVisibleAndDrawn(w);
- }, true);
+ makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());
setupKeyguardOccluded();
backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 233a207..84d42d42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -18,16 +18,20 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
+import static com.android.server.wm.utils.LastCallVerifier.lastCall;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import android.graphics.Rect;
@@ -35,8 +39,9 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.utils.MockAnimationAdapter;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -118,102 +123,168 @@
}
}
- private MockSurfaceBuildingContainer mHost;
- private Dimmer mDimmer;
- private SurfaceControl.Transaction mTransaction;
- private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter;
+ static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory {
+ public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec,
+ SurfaceAnimationRunner runner) {
+ return sTestAnimation;
+ }
+ }
- private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter {
+ private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {
@Override
public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t,
- AnimationAdapter anim, boolean hidden, @AnimationType int type) {
+ AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {
surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);
}
}
+ private MockSurfaceBuildingContainer mHost;
+ private Dimmer mDimmer;
+ private SurfaceControl.Transaction mTransaction;
+ private TestWindowContainer mChild;
+ private static AnimationAdapter sTestAnimation;
+ private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter;
+
@Before
public void setUp() throws Exception {
mHost = new MockSurfaceBuildingContainer(mWm);
- mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
mTransaction = spy(StubTransaction.class);
- mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
+ mChild = new TestWindowContainer(mWm);
+ if (Flags.dimmerRefactor()) {
+ sTestAnimation = spy(new MockAnimationAdapter());
+ mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
+ } else {
+ sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
+ mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter);
+ }
}
@Test
public void testUpdateDimsAppliesCrop() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
+ mDimmer.dimAbove(mChild, alpha);
int width = 100;
int height = 300;
- mDimmer.mDimState.mDimBounds.set(0, 0, width, height);
+ mDimmer.getDimBounds().set(0, 0, width, height);
mDimmer.updateDims(mTransaction);
- verify(mTransaction).setWindowCrop(getDimLayer(), width, height);
- verify(mTransaction).show(getDimLayer());
+ verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height);
+ verify(mTransaction).show(mDimmer.getDimLayer());
}
@Test
- public void testDimAboveWithChildCreatesSurfaceAboveChild() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
-
+ public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, alpha);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+
+ assertNotNull("Dimmer should have created a surface", dimLayer);
+
+ mDimmer.updateDims(mTransaction);
+ verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1);
+ verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+ }
+
+ @Test
+ public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ final float alpha = 0.8f;
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, alpha);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1);
+ verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1);
}
@Test
- public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ final float alpha = 0.7f;
+ mHost.addChild(mChild, 0);
+ mDimmer.dimBelow(mChild, alpha, 50);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
- final float alpha = 0.8f;
- mDimmer.dimBelow(child, alpha, 0);
- SurfaceControl dimLayer = getDimLayer();
+ assertNotNull("Dimmer should have created a surface", dimLayer);
+
+ mDimmer.updateDims(mTransaction);
+ verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1);
+ verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha);
+ verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50);
+ }
+
+ @Test
+ public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ final float alpha = 0.7f;
+ mHost.addChild(mChild, 0);
+ mDimmer.dimBelow(mChild, alpha, 50);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha);
- verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
+ verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);
}
@Test
- public void testDimBelowWithChildSurfaceDestroyedWhenReset() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ // Dim once
+ mDimmer.dimBelow(mChild, alpha, 0);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ // Reset, and don't dim
+ mDimmer.resetDimStates();
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction).show(dimLayer);
+ verify(mTransaction).remove(dimLayer);
+ }
+
+ @Test
+ public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mChild, alpha);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction);
- verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any(
- SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+ verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class),
+ any(SurfaceControl.Transaction.class), any(AnimationAdapter.class),
+ anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
verify(mHost.getPendingTransaction()).remove(dimLayer);
}
@Test
public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- SurfaceControl dimLayer = getDimLayer();
+ // Dim once
+ mDimmer.dimBelow(mChild, alpha, 0);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ // Reset and dim again
mDimmer.resetDimStates();
- mDimmer.dimAbove(child, alpha);
-
+ mDimmer.dimAbove(mChild, alpha);
mDimmer.updateDims(mTransaction);
verify(mTransaction).show(dimLayer);
verify(mTransaction, never()).remove(dimLayer);
@@ -221,14 +292,12 @@
@Test
public void testDimUpdateWhileDimming() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
-
+ mHost.addChild(mChild, 0);
final float alpha = 0.8f;
- mDimmer.dimAbove(child, alpha);
- final Rect bounds = mDimmer.mDimState.mDimBounds;
+ mDimmer.dimAbove(mChild, alpha);
+ final Rect bounds = mDimmer.getDimBounds();
- SurfaceControl dimLayer = getDimLayer();
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
bounds.set(0, 0, 10, 10);
mDimmer.updateDims(mTransaction);
verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height());
@@ -242,41 +311,56 @@
}
@Test
- public void testRemoveDimImmediately() {
- TestWindowContainer child = new TestWindowContainer(mWm);
- mHost.addChild(child, 0);
-
- mDimmer.dimAbove(child, 1);
- SurfaceControl dimLayer = getDimLayer();
+ public void testRemoveDimImmediately_Smooth() {
+ assumeTrue(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, 1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
mDimmer.updateDims(mTransaction);
verify(mTransaction, times(1)).show(dimLayer);
- reset(mSurfaceAnimatorStarter);
+ reset(sTestAnimation);
mDimmer.dontAnimateExit();
mDimmer.resetDimStates();
mDimmer.updateDims(mTransaction);
- verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any(
- SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
+ verify(sTestAnimation, never()).startAnimation(
+ any(SurfaceControl.class), any(SurfaceControl.Transaction.class),
+ anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class));
+ verify(mTransaction).remove(dimLayer);
+ }
+
+ @Test
+ public void testRemoveDimImmediately_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
+ mHost.addChild(mChild, 0);
+ mDimmer.dimAbove(mChild, 1);
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
+ mDimmer.updateDims(mTransaction);
+ verify(mTransaction, times(1)).show(dimLayer);
+
+ reset(sSurfaceAnimatorStarter);
+ mDimmer.dontAnimateExit();
+ mDimmer.resetDimStates();
+ mDimmer.updateDims(mTransaction);
+ verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class),
+ any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),
eq(ANIMATION_TYPE_DIMMER));
verify(mTransaction).remove(dimLayer);
}
@Test
- public void testDimmerWithBlurUpdatesTransaction() {
+ public void testDimmerWithBlurUpdatesTransaction_Legacy() {
+ assumeFalse(Flags.dimmerRefactor());
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
final int blurRadius = 50;
mDimmer.dimBelow(child, 0, blurRadius);
- SurfaceControl dimLayer = getDimLayer();
+ SurfaceControl dimLayer = mDimmer.getDimLayer();
assertNotNull("Dimmer should have created a surface", dimLayer);
verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);
verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);
}
-
- private SurfaceControl getDimLayer() {
- return mDimmer.mDimState.mDimLayer;
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2bf1385..6235b3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,7 +26,9 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1655,6 +1657,127 @@
assertEquals(frontMostTaskFragment, tf0);
}
+ @Test
+ public void testApplyTransaction_reorderToBottomOfTask() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+ final Task task = createTask(mDisplayContent);
+ // Create a non-embedded Activity at the bottom.
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ final TaskFragment tf0 = createTaskFragment(task);
+ final TaskFragment tf1 = createTaskFragment(task);
+ // Create a non-embedded Activity at the top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+
+ // Ensure correct order of the children before the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+ // Reorder TaskFragment to bottom
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build();
+ mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Ensure correct order of the children after the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf0, task.getChildAt(2).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(0).asTaskFragment());
+ }
+
+ @Test
+ public void testApplyTransaction_reorderToTopOfTask() {
+ mController.unregisterOrganizer(mIOrganizer);
+ mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+ final Task task = createTask(mDisplayContent);
+ // Create a non-embedded Activity at the bottom.
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ final TaskFragment tf0 = createTaskFragment(task);
+ final TaskFragment tf1 = createTaskFragment(task);
+ // Create a non-embedded Activity at the top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+
+ // Ensure correct order of the children before the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+ // Reorder TaskFragment to top
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_REORDER_TO_TOP_OF_TASK).build();
+ mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Ensure correct order of the children after the operation
+ assertEquals(tf0, task.getChildAt(3).asTaskFragment());
+ assertEquals(topActivity, task.getChildAt(2).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+ }
+
+ @Test
+ public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() {
+ testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+ OP_TYPE_REORDER_TO_BOTTOM_OF_TASK);
+ }
+
+ @Test
+ public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() {
+ testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+ OP_TYPE_REORDER_TO_TOP_OF_TASK);
+ }
+
+ private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+ @TaskFragmentOperation.OperationType int opType) {
+ final Task task = createTask(mDisplayContent);
+ // Create a non-embedded Activity at the bottom.
+ final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+ final TaskFragment tf0 = createTaskFragment(task);
+ final TaskFragment tf1 = createTaskFragment(task);
+ // Create a non-embedded Activity at the top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+ .setTask(task)
+ .build();
+
+ // Ensure correct order of the children before the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+ // Apply reorder transaction, which is expected to fail for non-system organizer.
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ opType).build();
+ mTransaction
+ .addTaskFragmentOperation(tf0.getFragmentToken(), operation)
+ .setErrorCallbackToken(mErrorToken);
+ assertApplyTransactionAllowed(mTransaction);
+ // The pending event will be dispatched on the handler (from requestTraversal).
+ waitHandlerIdle(mWm.mAnimationHandler);
+
+ assertTaskFragmentErrorTransaction(opType, SecurityException.class);
+
+ // Ensure no change to the order of the children after the operation
+ assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+ assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+ assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+ assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+ }
+
/**
* Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
* {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
@@ -1782,6 +1905,19 @@
assertEquals(activityToken, change.getActivityToken());
}
+ /** Setups an embedded TaskFragment. */
+ private TaskFragment createTaskFragment(Task task) {
+ final IBinder token = new Binder();
+ TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(token)
+ .setOrganizer(mOrganizer)
+ .createActivityCount(1)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment);
+ return taskFragment;
+ }
+
/** Setups an embedded TaskFragment in a PIP Task. */
private void setupTaskFragmentInPip() {
mTaskFragment = new TaskFragmentBuilder(mAtm)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5205bb0..7822071 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -37,6 +37,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -687,4 +689,24 @@
tf0.setIsolatedNav(true);
assertTrue(tf0.isIsolatedNav());
}
+
+ @Test
+ public void testGetDimBounds() {
+ final Task task = mTaskFragment.getTask();
+ final Rect taskBounds = task.getBounds();
+ mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10,
+ taskBounds.top + 10);
+ final Rect taskFragmentBounds = mTaskFragment.getBounds();
+
+ // Return Task bounds if dimming on parent Task.
+ final Rect dimBounds = new Rect();
+ mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+ mTaskFragment.getDimBounds(dimBounds);
+ assertEquals(taskBounds, dimBounds);
+
+ // Return TF bounds by default.
+ mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT);
+ mTaskFragment.getDimBounds(dimBounds);
+ assertEquals(taskFragmentBounds, dimBounds);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7168670..0b77fd8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -40,6 +40,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.testing.Assert.assertThrows;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
@@ -58,6 +59,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
@@ -77,11 +79,13 @@
import android.view.Display;
import android.view.SurfaceControl;
import android.view.WindowInsets;
+import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.IWindowContainerTransactionCallback;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
+import android.window.TaskFragmentOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -579,6 +583,87 @@
}
@Test
+ public void testTaskFragmentHiddenAndFocusableChanges() {
+ removeGlobalMinSizeRestriction();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ final TaskFragmentOrganizer organizer =
+ createTaskFragmentOrganizer(t, true /* isSystemOrganizer */);
+
+ final IBinder token = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(rootTask)
+ .setFragmentToken(token)
+ .setOrganizer(organizer)
+ .createActivityCount(1)
+ .build();
+
+ // Should be visible and focusable initially.
+ assertTrue(rootTask.shouldBeVisible(null));
+ assertTrue(taskFragment.shouldBeVisible(null));
+ assertTrue(taskFragment.isFocusable());
+ assertTrue(taskFragment.isTopActivityFocusable());
+
+ // Apply transaction to the TaskFragment hidden and not focusable.
+ t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+
+ // Should be not visible and not focusable after the transaction.
+ assertFalse(taskFragment.shouldBeVisible(null));
+ assertFalse(taskFragment.isFocusable());
+ assertFalse(taskFragment.isTopActivityFocusable());
+
+ // Apply transaction to the TaskFragment not hidden and focusable.
+ t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+ t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+
+ // Should be visible and focusable after the transaction.
+ assertTrue(taskFragment.shouldBeVisible(null));
+ assertTrue(taskFragment.isFocusable());
+ assertTrue(taskFragment.isTopActivityFocusable());
+ }
+
+ @Test
+ public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() {
+ removeGlobalMinSizeRestriction();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ final TaskFragmentOrganizer organizer =
+ createTaskFragmentOrganizer(t, false /* isSystemOrganizer */);
+
+ final IBinder token = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(rootTask)
+ .setFragmentToken(token)
+ .setOrganizer(organizer)
+ .createActivityCount(1)
+ .build();
+
+ assertTrue(rootTask.shouldBeVisible(null));
+ assertTrue(taskFragment.shouldBeVisible(null));
+
+ t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+
+ // Non-system organizers are not allow to update the hidden and focusable states.
+ assertThrows(SecurityException.class, () ->
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */)
+ );
+ }
+
+ @Test
public void testContainerTranslucentChanges() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
@@ -1600,4 +1685,20 @@
assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
}
}
+
+ @NonNull
+ private TaskFragmentOrganizer createTaskFragmentOrganizer(
+ @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer organizerInterface =
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+ mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .registerOrganizerInternal(
+ ITaskFragmentOrganizer.Stub.asInterface(
+ organizer.getOrganizerToken().asBinder()),
+ isSystemOrganizer);
+ t.setTaskFragmentOrganizer(organizerInterface);
+
+ return organizer;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
new file mode 100644
index 0000000..320d094
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java
@@ -0,0 +1,68 @@
+/*
+ * 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.server.wm.utils;
+
+import org.mockito.internal.verification.VerificationModeFactory;
+import org.mockito.internal.verification.api.VerificationData;
+import org.mockito.invocation.Invocation;
+import org.mockito.invocation.MatchableInvocation;
+import org.mockito.verification.VerificationMode;
+
+import java.util.List;
+
+/**
+ * Verifier to check that the last call of a method received the expected argument
+ */
+public class LastCallVerifier implements VerificationMode {
+
+ /**
+ * Allows comparing the expected invocation with the last invocation on the same method
+ */
+ public static LastCallVerifier lastCall() {
+ return new LastCallVerifier();
+ }
+
+ @Override
+ public void verify(VerificationData data) {
+ List<Invocation> invocations = data.getAllInvocations();
+ MatchableInvocation target = data.getTarget();
+ for (int i = invocations.size() - 1; i >= 0; i--) {
+ final Invocation invocation = invocations.get(i);
+ if (target.hasSameMethod(invocation)) {
+ if (target.matches(invocation)) {
+ return;
+ } else {
+ throw new LastCallMismatch(target.getInvocation(), invocation, invocations);
+ }
+ }
+ }
+ throw new RuntimeException(target + " never invoked");
+ }
+
+ @Override
+ public VerificationMode description(String description) {
+ return VerificationModeFactory.description(this, description);
+ }
+
+ static class LastCallMismatch extends RuntimeException {
+ LastCallMismatch(
+ Invocation expected, Invocation received, List<Invocation> allInvocations) {
+ super("Expected invocation " + expected + " but received " + received
+ + " as the last invocation.\nAll registered invocations:\n" + allInvocations);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
new file mode 100644
index 0000000..1a66970a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.server.wm.utils;
+
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.wm.AnimationAdapter;
+import com.android.server.wm.SurfaceAnimator;
+
+import java.io.PrintWriter;
+
+/**
+ * An empty animation adapter which just executes the finish callback
+ */
+public class MockAnimationAdapter implements AnimationAdapter {
+
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ // As the animation won't run, finish it immediately
+ finishCallback.onAnimationFinished(0, null);
+ }
+
+ @Override
+ public void onAnimationCancelled(SurfaceControl animationLeash) {}
+
+ @Override
+ public long getDurationHint() {
+ return 0;
+ }
+
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return 0;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {}
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto) {}
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 2e6278d..55b5d11 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2127,15 +2127,13 @@
}
private boolean canReportUsageStats() {
- final boolean isSystem = isCallingUidSystem();
- if (!Flags.reportUsageStatsPermission()) {
- // If the flag is disabled, do no check for the new permission and instead return
- // true only if the calling uid is system since System UID can always report stats.
- return isSystem;
+ if (isCallingUidSystem()) {
+ // System UID can always report UsageStats
+ return true;
}
- return isSystem
- || getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
- == PackageManager.PERMISSION_GRANTED;
+
+ return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS)
+ == PackageManager.PERMISSION_GRANTED;
}
private boolean hasObserverPermission() {
@@ -2627,9 +2625,12 @@
return;
}
- if (!canReportUsageStats()) {
- throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS"
- + " permission are allowed to call reportChooserSelection");
+ if (Flags.reportUsageStatsPermission()) {
+ if (!canReportUsageStats()) {
+ throw new SecurityException(
+ "Only the system or holders of the REPORT_USAGE_STATS"
+ + " permission are allowed to call reportChooserSelection");
+ }
}
// Verify if this package exists before reporting an event for it.
@@ -2649,9 +2650,17 @@
@Override
public void reportUserInteraction(String packageName, int userId) {
Objects.requireNonNull(packageName);
- if (!canReportUsageStats()) {
- throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS"
- + " permission are allowed to call reportUserInteraction");
+ if (Flags.reportUsageStatsPermission()) {
+ if (!canReportUsageStats()) {
+ throw new SecurityException(
+ "Only the system or holders of the REPORT_USAGE_STATS"
+ + " permission are allowed to call reportUserInteraction");
+ }
+ } else {
+ if (!isCallingUidSystem()) {
+ throw new SecurityException("Only system is allowed to call"
+ + " reportUserInteraction");
+ }
}
final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 971fc78..e20e4d2 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -202,6 +202,24 @@
&& mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
}
+ private static String barringTypeToString(@BarringType int barringType) {
+ return switch (barringType) {
+ case BARRING_TYPE_NONE -> "NONE";
+ case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL";
+ case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL";
+ case BARRING_TYPE_UNKNOWN -> "UNKNOWN";
+ default -> "UNKNOWN(" + barringType + ")";
+ };
+ }
+
+ @Override
+ public String toString() {
+ return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType)
+ + ", mIsConditionallyBarred=" + mIsConditionallyBarred
+ + ", mConditionalBarringFactor=" + mConditionalBarringFactor
+ + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}";
+ }
+
/** @hide */
public BarringServiceInfo(Parcel p) {
mBarringType = p.readInt();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 90fa69f..73220c3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15033,15 +15033,6 @@
@TestApi
public static final int HAL_SERVICE_IMS = 7;
- /**
- * HAL service type that supports the HAL APIs implementation of IRadioSatellite
- * {@link RadioSatelliteProxy}
- * @hide
- */
- @TestApi
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- public static final int HAL_SERVICE_SATELLITE = 8;
-
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"HAL_SERVICE_"},
@@ -15054,7 +15045,6 @@
HAL_SERVICE_SIM,
HAL_SERVICE_VOICE,
HAL_SERVICE_IMS,
- HAL_SERVICE_SATELLITE
})
public @interface HalService {}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 72e4389..a20f26c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -120,31 +120,6 @@
int BLOCKED_DUE_TO_CALL = 69; /* SMS is blocked due to call control */
int RF_HARDWARE_ISSUE = 70; /* RF HW issue is detected */
int NO_RF_CALIBRATION_INFO = 71; /* No RF calibration in device */
- int ENCODING_NOT_SUPPORTED = 72; /* The encoding scheme is not supported by
- either the network or the MS. */
- int FEATURE_NOT_SUPPORTED = 73; /* The requesting feature is not supported by
- the service provider. */
- int INVALID_CONTACT = 74; /* The contact to be added is either not
- existing or not valid. */
- int MODEM_INCOMPATIBLE = 75; /* The modem of the MS is not compatible with
- the service provider. */
- int NETWORK_TIMEOUT = 76; /* Modem timeout to receive ACK or response from
- network after sending a request to it. */
- int NO_SATELLITE_SIGNAL = 77; /* Modem fails to communicate with the satellite
- network since there is no satellite signal.*/
- int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78; /* The request cannot be performed since the
- subscriber's account balance is not
- sufficient. */
- int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79; /* The radio technology is not supported by the
- service provider. */
- int SUBSCRIBER_NOT_AUTHORIZED = 80; /* The subscription is not authorized to
- register with the service provider. */
- int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the
- Framework the satellite modem detects
- terrestrial signal, aborts the request, and
- switches to the terrestrial network. */
- int UNIDENTIFIED_SUBSCRIBER = 82; /* The subscriber is not registered with the
- service provider */
// Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
// reveal particular replacement for Generic failure
@@ -568,22 +543,7 @@
int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
- int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
- int RIL_REQUEST_SET_SATELLITE_POWER = 246;
- int RIL_REQUEST_GET_SATELLITE_POWER = 247;
- int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
- int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
- int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
- int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
- int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
- int RIL_REQUEST_GET_SATELLITE_MODE = 253;
- int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
- int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
- int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
- int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
- int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
- int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259;
- int RIL_REQUEST_SET_SATELLITE_PLMN = 260;
+ int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -645,13 +605,6 @@
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
- int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
- int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
- int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
- int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
- int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
- int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
- int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
/* The following unsols are not defined in RIL.h */
int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index 241e691..f61cce6 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
@Deprecated public class MockAccountManager {
@@ -202,7 +200,7 @@
method @Deprecated public int checkPermission(String, String);
method @Deprecated public int checkSignatures(String, String);
method @Deprecated public int checkSignatures(int, int);
- method public void clearInstantAppCookie();
+ method @Deprecated public void clearInstantAppCookie();
method @Deprecated public void clearPackagePreferredActivities(String);
method @Deprecated public String[] currentToCanonicalPackageNames(String[]);
method @Deprecated public void extendVerificationTimeout(int, int, long);
@@ -224,15 +222,15 @@
method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.ChangedPackages getChangedPackages(int);
+ method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);
method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);
method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();
method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);
method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method @Deprecated public String getInstallerPackageName(String);
- method public byte[] getInstantAppCookie();
- method public int getInstantAppCookieMaxBytes();
+ method @Deprecated public byte[] getInstantAppCookie();
+ method @Deprecated public int getInstantAppCookieMaxBytes();
method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
@@ -241,7 +239,7 @@
method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public android.content.pm.PackageInstaller getPackageInstaller();
+ method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();
method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @Deprecated public String[] getPackagesForUid(int);
method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int);
@@ -265,8 +263,8 @@
method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);
method @Deprecated public boolean hasSystemFeature(String);
method @Deprecated public boolean hasSystemFeature(String, int);
- method public boolean isInstantApp();
- method public boolean isInstantApp(String);
+ method @Deprecated public boolean isInstantApp();
+ method @Deprecated public boolean isInstantApp(String);
method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);
method @Deprecated public boolean isSafeMode();
method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -283,11 +281,12 @@
method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);
method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int);
- method public void setApplicationCategoryHint(String, int);
+ method @Deprecated public void setApplicationCategoryHint(String, int);
method @Deprecated public void setApplicationEnabledSetting(String, int, int);
method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);
method @Deprecated public void setInstallerPackageName(String, String);
- method public void updateInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);
method @Deprecated public void verifyPendingInstall(int, int);
}
diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt
index fa2fbd2..0c800b6 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -11,7 +9,7 @@
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
method @Deprecated public String getDefaultBrowserPackageName(int);
method @Deprecated public boolean setDefaultBrowserPackageName(String, int);
- method public boolean setInstantAppCookie(@NonNull byte[]);
+ method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
}
}
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index 2b5132e..f350957 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -11,30 +9,30 @@
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
- method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public boolean arePermissionsIndividuallyControlled();
+ method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method @Deprecated public boolean arePermissionsIndividuallyControlled();
method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String);
- method public String getDefaultBrowserPackageNameAsUser(int);
- method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
- method public android.graphics.drawable.Drawable getInstantAppIcon(String);
- method public android.content.ComponentName getInstantAppInstallerComponent();
- method public android.content.ComponentName getInstantAppResolverSettingsComponent();
- method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
- method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
- method public int getIntentVerificationStatusAsUser(String, int);
- method public int getPermissionFlags(String, String, android.os.UserHandle);
- method public void grantRuntimePermission(String, String, android.os.UserHandle);
- method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
- method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
- method public void revokeRuntimePermission(String, String, android.os.UserHandle);
- method public boolean setDefaultBrowserPackageNameAsUser(String, int);
+ method @Deprecated public String getDefaultBrowserPackageNameAsUser(int);
+ method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+ method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String);
+ method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent();
+ method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent();
+ method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+ method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
+ method @Deprecated public int getIntentVerificationStatusAsUser(String, int);
+ method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle);
+ method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle);
+ method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
+ method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
+ method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
- method public void setUpdateAvailable(String, boolean);
- method public boolean updateIntentVerificationStatusAsUser(String, int, int);
- method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
- method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+ method @Deprecated public void setUpdateAvailable(String, boolean);
+ method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
+ method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
+ method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
}
}
diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/system-removed.txt
+++ b/test-mock/api/system-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 1752edc..9ed0108 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -1,6 +1,4 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
package android.test.mock {
public class MockContext extends android.content.Context {
@@ -8,13 +6,13 @@
}
@Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
- method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
- method public void clearCrossProfileIntentFilters(int);
- method public int getInstallReason(String, android.os.UserHandle);
- method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
- method public String[] getNamesForUids(int[]);
- method @NonNull public String getServicesSystemSharedLibraryPackageName();
- method @NonNull public String getSharedSystemSharedLibraryPackageName();
+ method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+ method @Deprecated public void clearCrossProfileIntentFilters(int);
+ method @Deprecated public int getInstallReason(String, android.os.UserHandle);
+ method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+ method @Deprecated public String[] getNamesForUids(int[]);
+ method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName();
+ method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();
}
}
diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/test-removed.txt
+++ b/test-mock/api/test-removed.txt
@@ -1,3 +1 @@
// Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 359845d..47d6d23 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -36,7 +36,8 @@
/**
* Test launching a secondary Activity into Picture-In-Picture mode.
*
- * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom
* right corner on screen.
*
* To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
@@ -63,7 +64,16 @@
}
}
- /** Main and secondary activity start from a split each taking half of the screen. */
+ /**
+ * We expect the background layer to be visible during this transition.
+ */
+ @Presubmit
+ @Test
+ override fun backgroundLayerNeverVisible(): Unit {}
+
+ /**
+ * Main and secondary activity start from a split each taking half of the screen.
+ */
@Presubmit
@Test
fun layersStartFromEqualSplit() {
@@ -109,7 +119,7 @@
.isVisible(TRANSITION_SNAPSHOT)
.isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
- .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true)
}
flicker.assertLayersEnd {
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index a617876..226e2fad 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -7,9 +7,38 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+// Visibility only for ravenwood prototype uses.
+genrule_defaults {
+ name: "hoststubgen-for-prototype-only-genrule",
+ visibility: [
+ ":__subpackages__",
+ "//frameworks/base/ravenwood:__subpackages__",
+ ],
+}
+
+// Visibility only for ravenwood prototype uses.
+java_defaults {
+ name: "hoststubgen-for-prototype-only-java",
+ visibility: [
+ ":__subpackages__",
+ "//frameworks/base/ravenwood:__subpackages__",
+ ],
+}
+
+// Visibility only for ravenwood prototype uses.
+filegroup_defaults {
+ name: "hoststubgen-for-prototype-only-filegroup",
+ visibility: [
+ ":__subpackages__",
+ "//frameworks/base/ravenwood:__subpackages__",
+ ],
+}
+
// This library contains the standard hoststubgen annotations.
+// This is only for the prototype. The productionized version is "ravenwood-annotations".
java_library {
name: "hoststubgen-annotations",
+ defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"annotations-src/**/*.java",
],
@@ -18,7 +47,6 @@
// Seems like we need it to avoid circular deps.
// Copied it from "app-compat-annotations".
sdk_version: "core_current",
- visibility: ["//visibility:public"],
}
// This library contains helper classes used in the host side test environment at runtime.
@@ -30,11 +58,6 @@
],
libs: [
"junit",
- "ow2-asm",
- "ow2-asm-analysis",
- "ow2-asm-commons",
- "ow2-asm-tree",
- "ow2-asm-util",
],
static_libs: [
"guava",
@@ -60,12 +83,13 @@
}
// File that contains the standard command line argumetns to hoststubgen.
+// This is only for the prototype. The productionized version is "ravenwood-standard-options".
filegroup {
name: "hoststubgen-standard-options",
+ defaults: ["hoststubgen-for-prototype-only-filegroup"],
srcs: [
"hoststubgen-standard-options.txt",
],
- visibility: ["//visibility:public"],
}
hoststubgen_common_options = "$(location hoststubgen) " +
@@ -98,7 +122,6 @@
"hoststubgen_keep_all.txt",
"hoststubgen_dump.txt",
],
- // visibility: ["//visibility:public"],
}
// Generate the stub/impl from framework-all, with hidden APIs.
@@ -116,8 +139,10 @@
}
// Extract the stub jar from "framework-all-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-hidden-api-host-stub",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-hidden-api-host{host_stub.jar}",
@@ -125,12 +150,13 @@
out: [
"host_stub.jar",
],
- visibility: ["//visibility:public"],
}
// Extract the impl jar from "framework-all-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-hidden-api-host-impl",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-hidden-api-host{host_impl.jar}",
@@ -138,11 +164,11 @@
out: [
"host_impl.jar",
],
- visibility: ["//visibility:public"],
}
// Generate the stub/impl from framework-all, with only public/system/test APIs, without
// hidden APIs.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-test-api-host",
defaults: ["hoststubgen-command-defaults"],
@@ -159,8 +185,10 @@
}
// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-test-api-host-stub",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-test-api-host{host_stub.jar}",
@@ -168,12 +196,13 @@
out: [
"host_stub.jar",
],
- visibility: ["//visibility:public"],
}
// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
+// This is only for the prototype. Do not use it in "productionized" build rules.
java_genrule_host {
name: "framework-all-test-api-host-impl",
+ defaults: ["hoststubgen-for-prototype-only-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-all-test-api-host{host_impl.jar}",
@@ -181,7 +210,6 @@
out: [
"host_impl.jar",
],
- visibility: ["//visibility:public"],
}
// This library contains helper classes to build hostside tests/targets.
@@ -191,6 +219,7 @@
// Ideally this library should be empty.
java_library_host {
name: "hoststubgen-helper-framework-buildtime",
+ defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-buildtime-src/**/*.java",
],
@@ -200,13 +229,13 @@
"framework-all-hidden-api-host-impl",
"junit",
],
- visibility: ["//visibility:public"],
}
// This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
// that are needed at runtime.
java_library_host {
name: "hoststubgen-helper-framework-runtime",
+ defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-runtime-src/**/*.java",
],
@@ -214,7 +243,6 @@
"hoststubgen-helper-runtime",
"framework-all-hidden-api-host-impl",
],
- visibility: ["//visibility:public"],
}
// Defaults for host side test modules.
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
index 4c37579..7ada961 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
@@ -17,8 +17,6 @@
import static java.lang.annotation.ElementType.TYPE;
-import org.objectweb.asm.Type;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HostStubGenProcessedKeepClass {
- String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class);
+ String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class);
String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
index 34e0030..e598da0a 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
@@ -17,8 +17,6 @@
import static java.lang.annotation.ElementType.TYPE;
-import org.objectweb.asm.Type;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -29,6 +27,6 @@
@Target({TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HostStubGenProcessedStubClass {
- String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class);
+ String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class);
String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index d176b5e..9f83597 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -15,8 +15,6 @@
*/
package com.android.hoststubgen.hosthelper;
-import org.objectweb.asm.Type;
-
import java.io.PrintStream;
import java.lang.StackWalker.Option;
import java.lang.reflect.Method;
@@ -32,7 +30,15 @@
private HostTestUtils() {
}
- public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class);
+ /**
+ * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM
+ * in this JAR.
+ */
+ public static String getInternalName(final Class<?> clazz) {
+ return clazz.getName().replace('.', '/');
+ }
+
+ public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);
/** If true, we won't print method call log. */
private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index 722905f..6bf074b 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -16,14 +16,8 @@
source "${0%/*}"/../../common.sh
-#**********************************************************************************************
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#**********************************************************************************************
-
# This scripts run the "tiny-framework" test, but does most stuff from the command line, using
# the native java and javac commands.
-# This is useful to
-
debug=0
while getopts "d" opt; do
@@ -61,7 +55,7 @@
test_compile_classpaths=(
$SOONG_INT/external/junit/junit/android_common/combined/junit.jar
- $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar
+ $ANDROID_HOST_OUT/framework/truth-prebuilt.jar
)
test_runtime_classpaths=(
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 246d065..cd604ff 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -23,6 +23,10 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
public class TinyFrameworkClassTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -140,4 +144,42 @@
assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
.doesNotContain(TinyFrameworkNestedClasses.class);
}
+
+ /**
+ * Test to try accessing JDK private fields using reflections + setAccessible(true),
+ * which is now disallowed due to Java Modules, unless you run the javacommand with.
+ * --add-opens=java.base/java.io=ALL-UNNAMED
+ *
+ * You can try it from the command line, like:
+ * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testFileDescriptor() throws Exception {
+ var fd = FileDescriptor.out;
+
+ // Get the FD value directly from the private field.
+ // This is now prohibited due to Java Modules.
+ // It throws:
+ // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa
+
+ thrown.expect(java.lang.reflect.InaccessibleObjectException.class);
+
+ // Access the private field.
+ final Field f = FileDescriptor.class.getDeclaredField("fd");
+ final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class);
+ f.setAccessible(true);
+ m.setAccessible(true);
+
+ assertThat(f.get(fd)).isEqualTo(1);
+
+ // Set
+ f.set(fd, 2);
+ assertThat(f.get(fd)).isEqualTo(2);
+
+ // Call the package private method, set(int).
+ m.invoke(fd, 0);
+ assertThat(f.get(fd)).isEqualTo(0);
+ }
}
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 2e9cf42..8bc88de 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -34,8 +34,7 @@
run ./hoststubgen/test-framework/run-test-without-atest.sh
-#This script is broken because it relies on soong intermediate files, which seem to have moved.
-#run ./hoststubgen/test-tiny-framework/run-test-manually.sh
+run ./hoststubgen/test-tiny-framework/run-test-manually.sh
run atest tiny-framework-dump-test
run ./scripts/build-framework-hostside-jars-and-extract.sh