Merge "Add Reason to removeRemoteInput Logs" into main
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/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/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/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index bfc1eec..20eae9a 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) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b6a98a5..884351b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -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..1eb2cd1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4180,7 +4180,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 +4194,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 +4222,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 +4242,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.
*
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/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/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/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/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..9caf87f 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -13,3 +13,10 @@
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"
+}
\ No newline at end of file
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/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/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/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/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/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/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b7c3662..4764f22 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -626,7 +626,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/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/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/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/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/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/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/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 50bc825..4a3314d 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,8 @@
*/
int mMinHeight;
- Dimmer mDimmer = new Dimmer(this);
+ Dimmer mDimmer = Flags.dimmerRefactor()
+ ? new SmoothDimmer(this) : new LegacyDimmer(this);
/** This task fragment will be removed when the cleanup of its children are done. */
private boolean mIsRemovalRequested;
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/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/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) {}
+}