diff options
294 files changed, 7866 insertions, 3495 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index df8f5819718d..36c2f01e842a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9635,9 +9635,16 @@ package android.companion { method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException; method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String); - method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); + method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String); - method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); + method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); + method @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int); + field public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0 + field public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1 + field public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2 + field public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3 + field public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4 + field public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5 field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; } @@ -26453,7 +26460,7 @@ package android.media.midi { method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public void onClose(); - method public void onDeviceStatusChanged(@Nullable android.media.midi.MidiDeviceStatus); + method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 77168e3a45b3..0185080c915d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -647,6 +647,7 @@ package android.app { field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; + field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index bcd43ea8a3e2..096ad5588c43 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2256,6 +2256,7 @@ public class AppOpsManager { * * @hide */ + @SystemApi public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index d189bab85195..2c428efe4edb 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -236,6 +236,7 @@ interface IActivityTaskManager { * {@link android.view.WindowManagerPolicyConstants#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE} * etc. */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD)") void keyguardGoingAway(int flags); void suppressResizeConfigChanges(boolean suppress); diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 723c5641e1a3..5d7993d843e4 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -2929,19 +2929,16 @@ public class WallpaperManager { ComponentName cn = null; String[] cmfWallpaperMap = context.getResources().getStringArray( com.android.internal.R.array.default_wallpaper_component_per_device_color); - if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) { - Log.d(TAG, "No CMF wallpaper config"); - return getDefaultWallpaperComponent(context); - } - - for (String entry : cmfWallpaperMap) { - String[] cmfWallpaper; - if (!TextUtils.isEmpty(entry)) { - cmfWallpaper = entry.split(","); - if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals( - cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) { - cn = ComponentName.unflattenFromString(cmfWallpaper[1]); - break; + if (cmfWallpaperMap != null && cmfWallpaperMap.length > 0) { + for (String entry : cmfWallpaperMap) { + String[] cmfWallpaper; + if (!TextUtils.isEmpty(entry)) { + cmfWallpaper = entry.split(","); + if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals( + cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) { + cn = ComponentName.unflattenFromString(cmfWallpaper[1]); + break; + } } } } @@ -2950,7 +2947,7 @@ public class WallpaperManager { cn = null; } - return cn; + return cn == null ? getDefaultWallpaperComponent(context) : cn; } private static boolean isComponentExist(Context context, ComponentName cn) { diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index f660377d7924..03e75e9fc483 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -17,6 +17,7 @@ package android.companion; +import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,13 +32,14 @@ import android.util.Log; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; /** - * A service that receives calls from the system when the associated companion device appears - * nearby or is connected, as well as when the device is no longer "present" or connected. - * See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}. + * A service that receives calls from the system with device events. + * See {@link #onDeviceEvent(AssociationInfo, int)}. * * <p> * Companion applications must create a service that {@code extends} @@ -121,6 +123,57 @@ public abstract class CompanionDeviceService extends Service { */ public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; + /** @hide */ + @IntDef(prefix = {"DEVICE_EVENT"}, value = { + DEVICE_EVENT_BLE_APPEARED, + DEVICE_EVENT_BLE_DISAPPEARED, + DEVICE_EVENT_BT_CONNECTED, + DEVICE_EVENT_BT_DISCONNECTED, + DEVICE_EVENT_SELF_MANAGED_APPEARED, + DEVICE_EVENT_SELF_MANAGED_DISAPPEARED + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceEvent {} + + /** + * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback + * with this event if the device comes into BLE range. + */ + public static final int DEVICE_EVENT_BLE_APPEARED = 0; + + /** + * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback + * with this event if the device is no longer in BLE range. + */ + public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; + + /** + * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback + * with this event when the bluetooth device is connected. + */ + public static final int DEVICE_EVENT_BT_CONNECTED = 2; + + /** + * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback + * with this event if the bluetooth device is disconnected. + */ + public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; + + /** + * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will + * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a + * device has appeared on its own. + */ + public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; + + /** + * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will + * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a + * device has disappeared on its own. + */ + public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; + private final Stub mRemote = new Stub(); /** @@ -251,7 +304,10 @@ public abstract class CompanionDeviceService extends Service { * Called by system whenever a device associated with this app is connected. * * @param associationInfo A record for the companion device. + * + * @deprecated please override {@link #onDeviceEvent(AssociationInfo, int)} instead. */ + @Deprecated @MainThread public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) { if (!associationInfo.isSelfManaged()) { @@ -263,7 +319,10 @@ public abstract class CompanionDeviceService extends Service { * Called by system whenever a device associated with this app is disconnected. * * @param associationInfo A record for the companion device. + * + * @deprecated please override {@link #onDeviceEvent(AssociationInfo, int)} instead. */ + @Deprecated @MainThread public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) { if (!associationInfo.isSelfManaged()) { @@ -271,6 +330,30 @@ public abstract class CompanionDeviceService extends Service { } } + /** + * Called by the system during device events. + * + * <p>E.g. Event {@link #DEVICE_EVENT_BLE_APPEARED} will be called when the associated + * companion device comes into BLE range. + * <p>Event {@link #DEVICE_EVENT_BLE_DISAPPEARED} will be called when the associated + * companion device is no longer in BLE range. + * <p> Event {@link #DEVICE_EVENT_BT_CONNECTED} will be called when the associated + * companion device is connected. + * <p>Event {@link #DEVICE_EVENT_BT_DISCONNECTED} will be called when the associated + * companion device is disconnected. + * Note that app must receive {@link #DEVICE_EVENT_BLE_APPEARED} first before + * {@link #DEVICE_EVENT_BLE_DISAPPEARED} and {@link #DEVICE_EVENT_BT_CONNECTED} + * before {@link #DEVICE_EVENT_BT_DISCONNECTED}. + * + * @param associationInfo A record for the companion device. + * @param event Associated companion device's event. + */ + @MainThread + public void onDeviceEvent(@NonNull AssociationInfo associationInfo, + @DeviceEvent int event) { + // Do nothing. Companion apps can override this function. + } + @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { @@ -304,5 +387,11 @@ public abstract class CompanionDeviceService extends Service { public void onDeviceDisappeared(AssociationInfo associationInfo) { mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo)); } + + @Override + public void onDeviceEvent(AssociationInfo associationInfo, int event) { + mMainHandler.postAtFrontOfQueue( + () -> mService.onDeviceEvent(associationInfo, event)); + } } } diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl index fa6850882132..2a311bf1152f 100644 --- a/core/java/android/companion/ICompanionDeviceService.aidl +++ b/core/java/android/companion/ICompanionDeviceService.aidl @@ -22,4 +22,5 @@ import android.companion.AssociationInfo; oneway interface ICompanionDeviceService { void onDeviceAppeared(in AssociationInfo associationInfo); void onDeviceDisappeared(in AssociationInfo associationInfo); + void onDeviceEvent(in AssociationInfo associationInfo, int state); } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index a0bbeb5f4bfc..c86ccfdaa7d4 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -1483,6 +1483,12 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // proper SQL syntax for us. SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder(); + // Guard against SQL injection attacks + qBuilder.setStrict(true); + qBuilder.setProjectionMap(MAP_OF_QUERYABLE_COLUMNS); + qBuilder.setStrictColumns(true); + qBuilder.setStrictGrammar(true); + // Set the table we're querying. qBuilder.setTables(DATABASE_TABLE_NAME); @@ -1546,6 +1552,12 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // proper SQL syntax for us. SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder(); + // Guard against SQL injection attacks + qBuilder.setStrict(true); + qBuilder.setProjectionMap(MAP_OF_QUERYABLE_COLUMNS); + qBuilder.setStrictColumns(true); + qBuilder.setStrictGrammar(true); + // Set the table we're querying. qBuilder.setTables(DATABASE_TABLE_NAME); diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java index 518744e33da4..a86c0c9a4463 100644 --- a/core/java/android/content/res/Element.java +++ b/core/java/android/content/res/Element.java @@ -746,7 +746,6 @@ public class Element { case TAG_PROVIDER: case TAG_RECEIVER: case TAG_SERVICE: - case TAG_USES_LIBRARY: switch (name) { case TAG_ATTR_NAME: return true; @@ -776,8 +775,6 @@ public class Element { return index == R.styleable.AndroidManifestReceiver_name; case TAG_SERVICE: return index == R.styleable.AndroidManifestService_name; - case TAG_USES_LIBRARY: - return index == R.styleable.AndroidManifestUsesLibrary_name; default: return false; } diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index e9c59f55a418..2061c2bdd721 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -48,6 +48,15 @@ import java.util.regex.Pattern; /** * This is a convenience class that helps build SQL queries to be sent to * {@link SQLiteDatabase} objects. + * <p> + * This class is often used to compose a SQL query from client-supplied fragments. Best practice + * to protect against invalid or illegal SQL is to set the following: + * <ul> + * <li>{@link #setStrict} true. + * <li>{@link #setProjectionMap} with the list of queryable columns. + * <li>{@link #setStrictColumns} true. + * <li>{@link #setStrictGrammar} true. + * </ul> */ public class SQLiteQueryBuilder { private static final String TAG = "SQLiteQueryBuilder"; diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 3a660812fd24..c872516014db 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -3535,7 +3535,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. * They can be queried through * {@link android.hardware.camera2.CameraCharacteristics#get } with - * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }. + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }. * Unless reported by both * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and @@ -3550,13 +3550,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <ul> * <li> * <p>The mandatory stream combinations listed in - * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations } - * would not apply.</p> + * {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations} would not apply.</p> * </li> * <li> * <p>The bayer pattern of {@code RAW} streams when * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } - * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p> + * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p> * </li> * <li> * <p>The following keys will always be present:</p> @@ -3576,9 +3575,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see #SENSOR_PIXEL_MODE_DEFAULT diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1536376e4eb5..57f7bca1f67e 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -4460,7 +4460,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode. * They can be queried through * {@link android.hardware.camera2.CameraCharacteristics#get } with - * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }. + * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }. * Unless reported by both * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and @@ -4475,13 +4475,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <ul> * <li> * <p>The mandatory stream combinations listed in - * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations } - * would not apply.</p> + * {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations} would not apply.</p> * </li> * <li> * <p>The bayer pattern of {@code RAW} streams when * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION } - * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p> + * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p> * </li> * <li> * <p>The following keys will always be present:</p> @@ -4501,9 +4500,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * + * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION + * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION * @see #SENSOR_PIXEL_MODE_DEFAULT diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 299e7f112dd6..e6bdfe1b95c4 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -115,7 +115,6 @@ public class GraphicsEnvironment { private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; - private static final String SYSTEM_ANGLE_STRING = "system"; private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; @@ -196,16 +195,15 @@ public class GraphicsEnvironment { } /** - * Query to determine the ANGLE driver choice. + * Query to determine if ANGLE should be used */ - private String queryAngleChoice(Context context, Bundle coreSettings, - String packageName) { + private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { if (TextUtils.isEmpty(packageName)) { Log.v(TAG, "No package name specified; use the system driver"); - return ANGLE_GL_DRIVER_CHOICE_DEFAULT; + return false; } - return queryAngleChoiceInternal(context, coreSettings, packageName); + return shouldUseAngleInternal(context, coreSettings, packageName); } private int getVulkanVersion(PackageManager pm) { @@ -426,11 +424,10 @@ public class GraphicsEnvironment { * forces a choice; * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; */ - private String queryAngleChoiceInternal(Context context, Bundle bundle, - String packageName) { + private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) { // Make sure we have a good package name if (TextUtils.isEmpty(packageName)) { - return ANGLE_GL_DRIVER_CHOICE_DEFAULT; + return false; } // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE @@ -445,7 +442,7 @@ public class GraphicsEnvironment { } if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { Log.v(TAG, "Turn on ANGLE for all applications."); - return ANGLE_GL_DRIVER_CHOICE_ANGLE; + return true; } // Get the per-application settings lists @@ -468,8 +465,7 @@ public class GraphicsEnvironment { + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE - : ANGLE_GL_DRIVER_CHOICE_DEFAULT; + return mEnabledByGameMode; } // See if this application is listed in the per-application settings list @@ -477,8 +473,7 @@ public class GraphicsEnvironment { if (pkgIndex < 0) { Log.v(TAG, packageName + " is not listed in per-application setting"); - return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE - : ANGLE_GL_DRIVER_CHOICE_DEFAULT; + return mEnabledByGameMode; } mAngleOptInIndex = pkgIndex; @@ -489,14 +484,13 @@ public class GraphicsEnvironment { "ANGLE Developer option for '" + packageName + "' " + "set to: '" + optInValue + "'"); if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { - return ANGLE_GL_DRIVER_CHOICE_ANGLE; + return true; } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { - return ANGLE_GL_DRIVER_CHOICE_NATIVE; + return false; } else { // The user either chose default or an invalid value; go with the default driver or what // the game mode indicates - return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE - : ANGLE_GL_DRIVER_CHOICE_DEFAULT; + return mEnabledByGameMode; } } @@ -563,12 +557,8 @@ public class GraphicsEnvironment { */ private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager, String packageName) { - final String angleChoice = queryAngleChoice(context, bundle, packageName); - if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) { - return false; - } - if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { - nativeSetAngleInfo("", true, packageName, null); + + if (!shouldUseAngle(context, bundle, packageName)) { return false; } @@ -637,10 +627,10 @@ public class GraphicsEnvironment { Log.d(TAG, "ANGLE package libs: " + paths); } - // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the - // application package name and ANGLE features to use. + // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, + // and features to use. final String[] features = getAngleEglFeatures(context, bundle); - nativeSetAngleInfo(paths, false, packageName, features); + setAngleInfo(paths, false, packageName, features); return true; } @@ -662,10 +652,10 @@ public class GraphicsEnvironment { return false; } - // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with - // the application package name and ANGLE features to use. + // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, + // and features to use. final String[] features = getAngleEglFeatures(context, bundle); - nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features); + setAngleInfo("", true, packageName, features); return true; } @@ -946,8 +936,8 @@ public class GraphicsEnvironment { private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); - private static native void nativeSetAngleInfo(String path, boolean useNativeDriver, - String packageName, String[] features); + private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName, + String[] features); private static native boolean setInjectLayersPrSetDumpable(); private static native void nativeToggleAngleAsSystemDriver(boolean enabled); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cb46674a5ec2..2abf02ec3ed3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2120,6 +2120,21 @@ public final class Settings { "android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS"; /** + * Activity Action: Show app screen size list settings for user to override app aspect + * ratio. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Can include the following extra {@link android.content.Intent#EXTRA_PACKAGE_NAME} specifying + * the name of the package to scroll to in the page. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS = + "android.settings.MANAGE_USER_ASPECT_RATIO_SETTINGS"; + + /** * Activity Action: Show notification settings. * * @hide @@ -10104,6 +10119,13 @@ public final class Settings { public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled"; /** + * Internal collection of audio device inventory items + * The device item stored are {@link com.android.server.audio.AdiDeviceState} + * @hide + */ + public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory"; + + /** * Indicates whether notification display on the lock screen is enabled. * <p> * Type: int (0 for false, 1 for true) @@ -19147,6 +19169,12 @@ public final class Settings { * @hide */ public static final String WEAR_MEDIA_SESSIONS_PACKAGE = "wear_media_sessions_package"; + + /* + * Controls the launcher ui mode on wearable devices. + * @hide + */ + public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode"; } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index e47fab41f9ff..c7e54537703b 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -41,6 +41,9 @@ import android.app.Service; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -194,6 +197,18 @@ public abstract class WallpaperService extends Service { // TODO (b/287037772) remove this flag and the forceReport argument in reportVisibility private boolean mIsWearOs; + /** + * Wear products currently force a slight scaling transition to wallpapers + * when the QSS is opened. However, on Wear 6 (SDK 35) and above, 1P watch faces + * will be expected to either implement their own scaling, or to override this + * method to allow the WallpaperController to continue to scale for them. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final long WEAROS_WALLPAPER_HANDLES_SCALING = 272527315L; + static final class WallpaperCommand { String action; int x; @@ -601,7 +616,7 @@ public abstract class WallpaperService extends Service { * @hide */ public boolean shouldZoomOutWallpaper() { - return false; + return mIsWearOs && !CompatChanges.isChangeEnabled(WEAROS_WALLPAPER_HANDLES_SCALING); } /** diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING index b26a38be9ae4..a3a86fdb4b82 100644 --- a/core/java/android/util/apk/TEST_MAPPING +++ b/core/java/android/util/apk/TEST_MAPPING @@ -15,7 +15,7 @@ "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest" }, { - "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallerTest" + "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallTest" } ] } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 0a9bab504984..1cde7421fe6b 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -253,7 +253,11 @@ public class BatteryStatsHistory { public void traceCounter(@NonNull String name, int value) { Trace.traceCounter(Trace.TRACE_TAG_POWER, name, value); if (mShouldSetProperty) { - SystemProperties.set("debug.tracing." + name, Integer.toString(value)); + try { + SystemProperties.set("debug.tracing." + name, Integer.toString(value)); + } catch (RuntimeException e) { + Slog.e(TAG, "Failed to set debug.tracing." + name, e); + } } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 0c6d6f98ae24..965277c4635e 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -196,7 +196,8 @@ public final class Zygote { public static final int PROFILEABLE = 1 << 24; /** - * Enable ptrace. This is enabled on eng or userdebug builds, or if the app is debuggable. + * Enable ptrace. This is enabled on eng, if the app is debuggable, or if + * the persist.debug.ptrace.enabled property is set. */ public static final int DEBUG_ENABLE_PTRACE = 1 << 25; @@ -1020,20 +1021,35 @@ public final class Zygote { "persist.debug.dalvik.vm.jdwp.enabled").equals("1"); /** + * This will enable ptrace by default for all apps. It is OK to cache this property + * because we expect to reboot the system whenever this property changes + */ + private static final boolean ENABLE_PTRACE = SystemProperties.get( + "persist.debug.ptrace.enabled").equals("1"); + + /** * Applies debugger system properties to the zygote arguments. * - * For eng builds all apps are debuggable. On userdebug and user builds - * if persist.debug.dalvik.vm.jdwp.enabled is 1 all apps are - * debuggable. Otherwise, the debugger state is specified via the - * "--enable-jdwp" flag in the spawn request. + * For eng builds all apps are debuggable with JDWP and ptrace. + * + * On userdebug builds if persist.debug.dalvik.vm.jdwp.enabled + * is 1 all apps are debuggable with JDWP and ptrace. Otherwise, the + * debugger state is specified via the "--enable-jdwp" flag in the + * spawn request. + * + * On userdebug builds if persist.debug.ptrace.enabled is 1 all + * apps are debuggable with ptrace. * * @param args non-null; zygote spawner args */ static void applyDebuggerSystemProperty(ZygoteArguments args) { - if (Build.IS_ENG || ENABLE_JDWP) { + if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_JDWP)) { args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + // Also enable ptrace when JDWP is enabled for consistency with + // before persist.debug.ptrace.enabled existed. + args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; } - if (RoSystemProperties.DEBUGGABLE) { + if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_PTRACE)) { args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE; } } @@ -1057,7 +1073,8 @@ public final class Zygote { int peerUid = peer.getUid(); if (args.mInvokeWith != null && peerUid != 0 - && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { + && (args.mRuntimeFlags + & (Zygote.DEBUG_ENABLE_JDWP | Zygote.DEBUG_ENABLE_PTRACE)) == 0) { throw new ZygoteSecurityException("Peer is permitted to specify an " + "explicit invoke-with wrapper command only for debuggable " + "applications."); diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index 8fc30d1c248d..afc3cbd15f88 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -50,7 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver, +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle, jstring packageName, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars packageNameChars(env, packageName); @@ -73,7 +73,7 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useN } } - android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver, + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle, packageNameChars.c_str(), features); } @@ -118,7 +118,7 @@ const JNINativeMethod g_methods[] = { reinterpret_cast<void*>(setGpuStats_native)}, {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, - {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", + {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", reinterpret_cast<void*>(setAngleInfo_native)}, {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native)}, diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 64be87b510c1..7300772e31dc 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5565,10 +5565,6 @@ <!-- pdp data reject retry delay in ms --> <integer name="config_pdp_reject_retry_delay_ms">-1</integer> - <!-- Duration in milliseconds for device to vibrate on mash press on power - button. --> - <integer name="config_mashPressVibrateTimeOnPowerButton">0</integer> - <!-- Whether or not to enable the binder heavy hitter watcher by default --> <bool name="config_defaultBinderHeavyHitterWatcherEnabled">false</bool> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index b7a5bc826641..18abe7073757 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -199,4 +199,8 @@ <bool name="allow_clear_initial_attach_data_profile">false</bool> <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" /> + <!-- Boolean indicating whether TelephonyAnalytics module is active or not. --> + <bool name="telephony_analytics_switch">true</bool> + <java-symbol type="bool" name="telephony_analytics_switch" /> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3aae3caee8ce..e7499f7c3d38 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4454,6 +4454,7 @@ <java-symbol type="string" name="notification_history_title_placeholder" /> + <java-symbol type="dimen" name="config_wallpaperMinScale"/> <!-- The max scale for the wallpaper when it's zoomed in --> <java-symbol type="dimen" name="config_wallpaperMaxScale"/> @@ -4949,8 +4950,6 @@ <java-symbol type="array" name="config_builtInDisplayIsRoundArray" /> <java-symbol type="array" name="config_gnssParameters" /> - <java-symbol type="integer" name="config_mashPressVibrateTimeOnPowerButton" /> - <java-symbol type="string" name="config_systemGameService" /> <java-symbol type="string" name="config_supervisedUserCreationPackage"/> diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl index 893c87d7fe1b..354f10a9432a 100644 --- a/data/keyboards/Vendor_0957_Product_0001.kl +++ b/data/keyboards/Vendor_0957_Product_0001.kl @@ -43,6 +43,8 @@ key 9 8 key 10 9 key 11 0 +key usage 0x00070037 PERIOD + # custom keys key usage 0x000c01BB TV_INPUT key usage 0x000c0186 MACRO_1 WAKE @@ -51,7 +53,6 @@ key usage 0x000c0185 TV_TELETEXT key usage 0x000c0061 CAPTIONS key usage 0x000c01BD INFO -key usage 0x000c0037 PERIOD key usage 0x000c0069 PROG_RED key usage 0x000c006A PROG_GREEN diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml new file mode 100644 index 000000000000..02b707568cd0 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" /> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index fb1980a52601..7e0c2071dc86 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -78,6 +78,19 @@ android:layout_weight="1"/> <ImageButton + android:id="@+id/maximize_window" + android:layout_width="40dp" + android:layout_height="40dp" + android:padding="9dp" + android:layout_marginEnd="8dp" + android:contentDescription="@string/maximize_button_text" + android:src="@drawable/decor_desktop_mode_maximize_button_dark" + android:scaleType="fitCenter" + android:gravity="end" + android:background="@null" + android:tint="@color/desktop_mode_caption_maximize_button_dark"/> + + <ImageButton android:id="@+id/close_window" android:layout_width="40dp" android:layout_height="40dp" diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index f2a07857cd4a..b2ec98bc1b15 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -64,6 +64,8 @@ <color name="desktop_mode_caption_expand_button_dark">#48473A</color> <color name="desktop_mode_caption_close_button_light">#EFF1F2</color> <color name="desktop_mode_caption_close_button_dark">#1C1C17</color> + <color name="desktop_mode_caption_maximize_button_light">#EFF1F2</color> + <color name="desktop_mode_caption_maximize_button_dark">#1C1C17</color> <color name="desktop_mode_caption_app_name_light">#EFF1F2</color> <color name="desktop_mode_caption_app_name_dark">#1C1C17</color> <color name="desktop_mode_caption_menu_text_color">#191C1D</color> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index b0afa6840fe0..66b9ade6aea0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -821,13 +821,17 @@ public class BubbleController implements ConfigurationChangeListener, * @param interceptBack whether back should be intercepted or not. */ void updateWindowFlagsForBackpress(boolean interceptBack) { - if (mStackView != null && mAddedToWindowManager) { + if (mAddedToWindowManager) { mWmLayoutParams.flags = interceptBack ? 0 : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); + if (mStackView != null) { + mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); + } else if (mLayerView != null) { + mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams); + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 67185655f2d2..e6986012dd88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -312,9 +312,13 @@ public class BubbleExpandedView extends LinearLayout { + " bubble=" + getBubbleKey()); } if (mBubble != null) { - // Must post because this is called from a binder thread. - post(() -> mController.removeBubble( - mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED)); + mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); + } + if (mTaskView != null) { + // Release the surface + mTaskView.release(); + removeView(mTaskView); + mTaskView = null; } } @@ -1058,8 +1062,10 @@ public class BubbleExpandedView extends LinearLayout { } /** - * Cleans up anything related to the task and {@code TaskView}. If this view should be reused - * after this method is called, then + * Cleans up anything related to the task. The TaskView itself is released after the task + * has been removed. + * + * If this view should be reused after this method is called, then * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first. */ public void cleanUpExpandedState() { @@ -1081,10 +1087,7 @@ public class BubbleExpandedView extends LinearLayout { } } if (mTaskView != null) { - // Release the surface & other task view related things - mTaskView.release(); - removeView(mTaskView); - mTaskView = null; + mTaskView.setVisibility(GONE); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 76ca68bbfa75..517f9f2aba27 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -54,6 +54,15 @@ public class DesktopModeStatus { public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_change_display", false); + + /** + * Flag to indicate that desktop stashing is enabled. + * When enabled, swiping home from desktop stashes the open apps. Next app that launches, + * will be added to the desktop. + */ + private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( + "persist.wm.debug.desktop_stashing", false); + /** * Return {@code true} if desktop mode support is enabled */ @@ -84,6 +93,13 @@ public class DesktopModeStatus { } /** + * Return {@code true} if desktop task stashing is enabled when going home. + * Allows users to use home screen to add tasks to desktop. + */ + public static boolean isStashingEnabled() { + return IS_STASHING_ENABLED; + } + /** * Check if desktop mode is active * * @return {@code true} if active diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 9bc28a4626c4..b15fd912e32d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -150,20 +150,24 @@ class DesktopTasksController( * back to front during the launch. */ fun stashDesktopApps(displayId: Int) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") - desktopModeTaskRepository.setStashed(displayId, true) + if (DesktopModeStatus.isStashingEnabled()) { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") + desktopModeTaskRepository.setStashed(displayId, true) + } } /** * Clear the stashed state for the given display */ fun hideStashedDesktopApps(displayId: Int) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: hideStashedApps displayId=%d", - displayId - ) - desktopModeTaskRepository.setStashed(displayId, false) + if (DesktopModeStatus.isStashingEnabled()) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: hideStashedApps displayId=%d", + displayId + ) + desktopModeTaskRepository.setStashed(displayId, false) + } } /** Get number of tasks that are marked as visible */ @@ -172,9 +176,13 @@ class DesktopTasksController( } /** Move a task with given `taskId` to desktop */ - fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) { + fun moveToDesktop( + decor: DesktopModeWindowDecoration, + taskId: Int, + wct: WindowContainerTransaction = WindowContainerTransaction() + ) { shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { - task -> moveToDesktop(task, wct) + task -> moveToDesktop(decor, task, wct) } } @@ -182,6 +190,7 @@ class DesktopTasksController( * Move a task to desktop */ fun moveToDesktop( + decor: DesktopModeWindowDecoration, task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { @@ -195,7 +204,7 @@ class DesktopTasksController( addMoveToDesktopChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor) } else { shellTaskOrganizer.applyTransaction(wct) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 22929c763f27..16b23935559c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.app.ActivityManager; import android.graphics.PointF; @@ -36,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration; import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import java.util.ArrayList; @@ -60,6 +62,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; private MoveToDesktopAnimator mMoveToDesktopAnimator; + private DesktopModeWindowDecoration mDesktopModeWindowDecoration; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -128,6 +131,18 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition onAnimationEndCallback); } + /** + * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP + * @param wct WindowContainerTransaction for transition + * @param decor {@link DesktopModeWindowDecoration} of task being animated + */ + public void moveToDesktop(@NonNull WindowContainerTransaction wct, + DesktopModeWindowDecoration decor) { + mDesktopModeWindowDecoration = decor; + startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct, + null /* onAnimationEndCallback */); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @@ -167,136 +182,207 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE + if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - // Transitioning to freeform but keeping fullscreen bounds, so the crop is set - // to null and we don't require an animation - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, null); - - if (mMoveToDesktopAnimator == null - || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { - Slog.e(TAG, "No animator available for this transition"); - return false; - } - - // Calculate and set position of the task - final PointF position = mMoveToDesktopAnimator.getPosition(); - startT.setPosition(sc, position.x, position.y); - finishT.setPosition(sc, position.x, position.y); - - startT.apply(); - - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + return animateMoveToDesktop(change, startT, finishCallback); + } - return true; + if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return animateStartDragToDesktopMode(change, startT, finishT, finishCallback); } - Rect endBounds = change.getEndAbsBounds(); + final Rect endBounds = change.getEndAbsBounds(); if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM && !endBounds.isEmpty()) { - // This Transition animates a task to freeform bounds after being dragged into freeform - // mode and brings the remaining freeform tasks to front - final SurfaceControl sc = change.getLeash(); - startT.setWindowCrop(sc, endBounds.width(), - endBounds.height()); - startT.apply(); - - // End the animation that shrinks the window when task is first dragged from fullscreen - if (mMoveToDesktopAnimator != null) { - mMoveToDesktopAnimator.endAnimator(); - } - - // We want to find the scale of the current bounds relative to the end bounds. The - // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be - // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to - // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds - final ValueAnimator animator = - ValueAnimator.ofFloat( - MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - animator.addUpdateListener(animation -> { - final float animationValue = (float) animation.getAnimatedValue(); - t.setScale(sc, animationValue, animationValue); - - final float animationWidth = endBounds.width() * animationValue; - final float animationHeight = endBounds.height() * animationValue; - final int animationX = endBounds.centerX() - (int) (animationWidth / 2); - final int animationY = endBounds.centerY() - (int) (animationHeight / 2); - - t.setPosition(sc, animationX, animationY); - t.apply(); - }); - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); - } - }); - - animator.start(); - return true; + return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback, + endBounds); } if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - // This Transition animates a task to fullscreen after being dragged from the status - // bar and then released back into the status bar area - final SurfaceControl sc = change.getLeash(); - // Hide the first (fullscreen) frame because the animation will start from the smaller - // scale size. - startT.hide(sc) - .setWindowCrop(sc, endBounds.width(), endBounds.height()) - .apply(); + return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback, + endBounds); + } - if (mMoveToDesktopAnimator == null - || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { - Slog.e(TAG, "No animator available for this transition"); - return false; + return false; + } + + private boolean animateMoveToDesktop( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mDesktopModeWindowDecoration == null) { + Slog.e(TAG, "Window Decoration is not available for this transition"); + return false; + } + + final SurfaceControl leash = change.getLeash(); + final Rect startBounds = change.getStartAbsBounds(); + startT.setPosition(leash, startBounds.left, startBounds.right) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash); + mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds); + + final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(), + change.getStartAbsBounds(), change.getEndAbsBounds()); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final Rect animationValue = (Rect) animator.getAnimatedValue(); + t.setPosition(leash, animationValue.left, animationValue.right) + .setWindowCrop(leash, animationValue.width(), animationValue.height()) + .show(leash); + mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDesktopModeWindowDecoration.hideResizeVeil(); + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); } + }); + animator.start(); + return true; + } - // End the animation that shrinks the window when task is first dragged from fullscreen + private boolean animateStartDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Transitioning to freeform but keeping fullscreen bounds, so the crop is set + // to null and we don't require an animation + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, null); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // Calculate and set position of the task + final PointF position = mMoveToDesktopAnimator.getPosition(); + startT.setPosition(sc, position.x, position.y); + finishT.setPosition(sc, position.x, position.y); + + startT.apply(); + + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + + return true; + } + + private boolean animateFinalizeDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Rect endBounds) { + // This Transition animates a task to freeform bounds after being dragged into freeform + // mode and brings the remaining freeform tasks to front + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, endBounds.width(), + endBounds.height()); + startT.apply(); + + // End the animation that shrinks the window when task is first dragged from fullscreen + if (mMoveToDesktopAnimator != null) { mMoveToDesktopAnimator.endAnimator(); + } - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); - animator.setDuration(FREEFORM_ANIMATION_DURATION); - final SurfaceControl.Transaction t = mTransactionSupplier.get(); - - // Get position of the task - final float x = mMoveToDesktopAnimator.getPosition().x; - final float y = mMoveToDesktopAnimator.getPosition().y; - - animator.addUpdateListener(animation -> { - final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, x * (1 - scale), y * (1 - scale)) - .setScale(sc, scale, scale) - .show(sc) - .apply(); - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mOnAnimationFinishedCallback != null) { - mOnAnimationFinishedCallback.accept(finishT); - } - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + // We want to find the scale of the current bounds relative to the end bounds. The + // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be + // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to + // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds + final ValueAnimator animator = + ValueAnimator.ofFloat( + MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final float animationValue = (float) animation.getAnimatedValue(); + t.setScale(sc, animationValue, animationValue); + + final float animationWidth = endBounds.width() * animationValue; + final float animationHeight = endBounds.height() * animationValue; + final int animationX = endBounds.centerX() - (int) (animationWidth / 2); + final int animationY = endBounds.centerY() - (int) (animationHeight / 2); + + t.setPosition(sc, animationX, animationY); + t.apply(); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinishedCallback != null) { + mOnAnimationFinishedCallback.accept(finishT); } - }); - animator.start(); - return true; + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + + animator.start(); + return true; + } + private boolean animateCancelDragToDesktopMode( + @NonNull TransitionInfo.Change change, + @NonNull SurfaceControl.Transaction startT, + @NonNull SurfaceControl.Transaction finishT, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull Rect endBounds) { + // This Transition animates a task to fullscreen after being dragged from the status + // bar and then released back into the status bar area + final SurfaceControl sc = change.getLeash(); + // Hide the first (fullscreen) frame because the animation will start from the smaller + // scale size. + startT.hide(sc) + .setWindowCrop(sc, endBounds.width(), endBounds.height()) + .apply(); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; } - return false; + // End the animation that shrinks the window when task is first dragged from fullscreen + mMoveToDesktopAnimator.endAnimator(); + + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + + // Get position of the task + final float x = mMoveToDesktopAnimator.getPosition().x; + final float y = mMoveToDesktopAnimator.getPosition().y; + + animator.addUpdateListener(animation -> { + final float scale = (float) animation.getAnimatedValue(); + t.setPosition(sc, x * (1 - scale), y * (1 - scale)) + .setScale(sc, scale, scale) + .show(sc) + .apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mOnAnimationFinishedCallback != null) { + mOnAnimationFinishedCallback.accept(finishT); + } + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + animator.start(); + return true; } @Nullable diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 39b6675567e7..88a81fc291b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -604,7 +604,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } else if (change.getMode() == TRANSIT_CHANGE) { // Finish recents animation if the display is changed, so the default // transition handler can play the animation such as rotation effect. - if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) { + if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY) + && info.getType() == TRANSIT_CHANGE) { // This call to cancel will use the screenshots taken preemptively in // handleMidTransitionRequest() prior to the display changing cancel(mWillFinishToHome, true /* withScreenshots */, "display change"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 064af04cbc4e..a743e99d6954 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -317,18 +317,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { // we know about -- so leave clean-up here even if shell transitions are enabled. if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; - if (mListener != null) { - final int taskId = taskInfo.taskId; - mListenerExecutor.execute(() -> { - mListener.onTaskRemovalStarted(taskId); - }); - } - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); + final SurfaceControl taskLeash = mTaskLeash; + handleAndNotifyTaskRemoval(mTaskInfo); // Unparent the task when this surface is destroyed - mTransaction.reparent(mTaskLeash, null).apply(); + mTransaction.reparent(taskLeash, null).apply(); resetTaskInfo(); - mTaskViewBase.onTaskVanished(taskInfo); } @Override @@ -498,6 +492,20 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } } + /** Notifies listeners of a task being removed and stops intercepting back presses on it. */ + private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) { + if (taskInfo != null) { + if (mListener != null) { + final int taskId = taskInfo.taskId; + mListenerExecutor.execute(() -> { + mListener.onTaskRemovalStarted(taskId); + }); + } + mTaskViewBase.onTaskVanished(taskInfo); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false); + } + } + /** Returns the task info for the task in the TaskView. */ @Nullable public ActivityManager.RunningTaskInfo getTaskInfo() { @@ -523,18 +531,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { */ void cleanUpPendingTask() { if (mPendingInfo != null) { - if (mListener != null) { - final int taskId = mPendingInfo.taskId; - mListenerExecutor.execute(() -> { - mListener.onTaskRemovalStarted(taskId); - }); - } - mTaskViewBase.onTaskVanished(mPendingInfo); - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false); + final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo; + handleAndNotifyTaskRemoval(pendingInfo); // Make sure the task is removed WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.removeTask(mPendingInfo.token); + wct.removeTask(pendingInfo.token); mTaskViewTransitions.closeTaskView(wct, this); } resetTaskInfo(); @@ -559,16 +561,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * is used instead. */ void prepareCloseAnimation() { - if (mTaskToken != null) { - if (mListener != null) { - final int taskId = mTaskInfo.taskId; - mListenerExecutor.execute(() -> { - mListener.onTaskRemovalStarted(taskId); - }); - } - mTaskViewBase.onTaskVanished(mTaskInfo); - mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false); - } + handleAndNotifyTaskRemoval(mTaskInfo); resetTaskInfo(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index d978eafa97f3..d07d2b7b6db9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -346,7 +346,7 @@ public class TransitionAnimationHelper { .setFrameScale(1) .setPixelFormat(PixelFormat.RGBA_8888) .setChildrenOnly(true) - .setAllowProtected(true) + .setAllowProtected(false) .setCaptureSecureLayers(true) .build(); final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index e45dacf1189d..e2dce88d5958 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -168,6 +168,9 @@ public class Transitions implements RemoteCallable<Transitions>, public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 14; + /** Transition to animate task to desktop. */ + public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index ea9976da3229..2b19da2498a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -222,7 +222,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE - || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) { + || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE + || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) { mWindowDecorByTaskId.get(change.getTaskInfo().taskId) .addTransitionPausingRelayout(transition); } @@ -356,7 +357,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // App sometimes draws before the insets from WindowDecoration#relayout have // been added, so they must be added here mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); - mDesktopTasksController.get().moveToDesktop(mTaskId, wct); + decoration.incrementRelayoutBlock(); + mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct); } decoration.closeHandleMenu(); } else if (id == R.id.fullscreen_button) { @@ -372,6 +374,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId)); decoration.closeHandleMenu(); } + } else if (id == R.id.maximize_window) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize( + taskInfo, decoration)); + decoration.closeHandleMenu(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt index b67acd5c15bb..672e57aab5ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt @@ -28,6 +28,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button) private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window) private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button) + private val maximizeWindowButton: ImageButton = rootView.findViewById(R.id.maximize_window) private val appNameTextView: TextView = rootView.findViewById(R.id.application_name) private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon) @@ -37,6 +38,7 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( openMenuButton.setOnClickListener(onCaptionButtonClickListener) openMenuButton.setOnTouchListener(onCaptionTouchListener) closeWindowButton.setOnClickListener(onCaptionButtonClickListener) + maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener) closeWindowButton.setOnTouchListener(onCaptionTouchListener) appNameTextView.text = appName appIconImageView.setImageDrawable(appIcon) @@ -49,6 +51,8 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( closeWindowButton.imageTintList = ColorStateList.valueOf( getCaptionCloseButtonColor(taskInfo)) + maximizeWindowButton.imageTintList = ColorStateList.valueOf( + getCaptionMaximizeButtonColor(taskInfo)) expandMenuButton.imageTintList = ColorStateList.valueOf( getCaptionExpandButtonColor(taskInfo)) appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo)) @@ -70,6 +74,14 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( } } + private fun getCaptionMaximizeButtonColor(taskInfo: RunningTaskInfo): Int { + return if (shouldUseLightCaptionColors(taskInfo)) { + context.getColor(R.color.desktop_mode_caption_maximize_button_light) + } else { + context.getColor(R.color.desktop_mode_caption_maximize_button_dark) + } + } + private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int { return if (shouldUseLightCaptionColors(taskInfo)) { context.getColor(R.color.desktop_mode_caption_expand_button_light) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 5efa51b53970..421ad757f76a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -63,12 +63,8 @@ class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) : } /** Checks that the visible region of [pipApp] window always moves down during the animation. */ - @Presubmit - @Test - fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN) + @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN) /** Checks that the visible region of [pipApp] layer always moves down during the animation. */ - @Presubmit - @Test - fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN) + @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt index 2494054c1fae..e37d806c7a14 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit import org.junit.Test -class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { +open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt index 57943ec4c8fa..2a50912e0a5c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit import org.junit.Test -class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { +open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt index 6f0e202ae70f..d5da1a8b558c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider import org.junit.Test -class DismissSplitScreenByDividerGesturalNavLandscape : +open class DismissSplitScreenByDividerGesturalNavLandscape : DismissSplitScreenByDivider(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt index dac8fa24b289..7fdcb9be62ee 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider import org.junit.Test -class DismissSplitScreenByDividerGesturalNavPortrait : +open class DismissSplitScreenByDividerGesturalNavPortrait : DismissSplitScreenByDivider(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt index baecc161d227..308e954b86c1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome import org.junit.Test -class DismissSplitScreenByGoHomeGesturalNavLandscape : +open class DismissSplitScreenByGoHomeGesturalNavLandscape : DismissSplitScreenByGoHome(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt index 3063ea564b58..39e75bd25a71 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome import org.junit.Test -class DismissSplitScreenByGoHomeGesturalNavPortrait : +open class DismissSplitScreenByGoHomeGesturalNavPortrait : DismissSplitScreenByGoHome(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt index 41660ba68385..e18da17175c0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize import org.junit.Test -class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { +open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt index c41ffb786cd9..00d60e756ffa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize import org.junit.Test -class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { +open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt index afde55bf519f..d7efbc8c0fd4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test -class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : +open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt index 3765fc42d2c9..4eece3f62d10 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps import org.junit.Test -class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : +open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt index 1e128fd3e47f..d96b056d8753 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test -class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : +open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt index 7767872cb7fd..809b690e0861 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification import org.junit.Test -class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : +open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt index 4ca4bd1d6d8b..bbdf2d728494 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test -class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : +open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt index 2d9d258ecc54..5c29fd8fe57e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut import org.junit.Test -class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : +open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt index c9282acc5a2f..a7398ebf56e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test -class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : +open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt index 68c6d6cccec1..eae88ad4ad09 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar import org.junit.Test -class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : +open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt index 304529ec83fb..7e8ee04a28fa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview import org.junit.Test -class EnterSplitScreenFromOverviewGesturalNavLandscape : +open class EnterSplitScreenFromOverviewGesturalNavLandscape : EnterSplitScreenFromOverview(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt index 53a6b449df2b..9295c330b879 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview import org.junit.Test -class EnterSplitScreenFromOverviewGesturalNavPortrait : +open class EnterSplitScreenFromOverviewGesturalNavPortrait : EnterSplitScreenFromOverview(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt index a4678301be12..4b59e9fbd866 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test -class SwitchAppByDoubleTapDividerGesturalNavLandscape : +open class SwitchAppByDoubleTapDividerGesturalNavLandscape : SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt index 15242330a554..5ff36d4aabbb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider import org.junit.Test -class SwitchAppByDoubleTapDividerGesturalNavPortrait : +open class SwitchAppByDoubleTapDividerGesturalNavPortrait : SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt index 0389659457e5..c0cb7219437b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test -class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : +open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt index 7fadf184d74b..8c140884aa50 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp import org.junit.Test -class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : +open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt index 148cc522e64d..7b6614b81c11 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome import org.junit.Test -class SwitchBackToSplitFromHomeGesturalNavLandscape : +open class SwitchBackToSplitFromHomeGesturalNavLandscape : SwitchBackToSplitFromHome(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt index 0641fb03527c..5df5be9daa8b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome import org.junit.Test -class SwitchBackToSplitFromHomeGesturalNavPortrait : +open class SwitchBackToSplitFromHomeGesturalNavPortrait : SwitchBackToSplitFromHome(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt index 741f871e2c5f..9d63003bf2a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent import org.junit.Test -class SwitchBackToSplitFromRecentGesturalNavLandscape : +open class SwitchBackToSplitFromRecentGesturalNavLandscape : SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt index 778e2d6a048a..9fa04b208ad1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -22,7 +22,7 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent import org.junit.Test -class SwitchBackToSplitFromRecentGesturalNavPortrait : +open class SwitchBackToSplitFromRecentGesturalNavPortrait : SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt index dcdca70e0f11..9386aa2b2cf0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -22,7 +22,8 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs import org.junit.Test -class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) { +open class SwitchBetweenSplitPairsGesturalNavLandscape : + SwitchBetweenSplitPairs(Rotation.ROTATION_90) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt index 3c69311a7028..5ef21672bfe0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -22,7 +22,8 @@ import android.tools.common.Rotation import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs import org.junit.Test -class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) { +open class SwitchBetweenSplitPairsGesturalNavPortrait : + SwitchBetweenSplitPairs(Rotation.ROTATION_0) { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt index c6566f5bec80..9caab9b5182a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -24,7 +24,7 @@ import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner @RunWith(BlockJUnit4ClassRunner::class) -class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { +open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt index bb1a5025c5dc..bf484e5cef98 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -24,7 +24,7 @@ import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner @RunWith(BlockJUnit4ClassRunner::class) -class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { +open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt index 91e7d478557e..e59ed6491eca 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt @@ -25,8 +25,8 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.PipAppHelper -import com.android.wm.shell.flicker.utils.SplitScreenUtils import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase +import com.android.wm.shell.flicker.utils.SplitScreenUtils import com.android.wm.shell.flicker.utils.layerBecomesInvisible import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider @@ -45,8 +45,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBetweenSplitPairsNoPip (override val flicker: LegacyFlickerTest) : - SplitScreenBase(flicker) { +class SwitchBetweenSplitPairsNoPip(override val flicker: LegacyFlickerTest) : + SplitScreenBase(flicker) { val thirdApp = SplitScreenUtils.getSendNotification(instrumentation) val pipApp = PipAppHelper(instrumentation) @@ -77,74 +77,69 @@ class SwitchBetweenSplitPairsNoPip (override val flicker: LegacyFlickerTest) : } } - /** - * Checks that [pipApp] window won't enter pip - */ + /** Checks that [pipApp] window won't enter pip */ @Presubmit @Test fun notEnterPip() { flicker.assertWm { isNotPinned(pipApp) } } - /** - * Checks the [pipApp] task did not reshow during transition. - */ + /** Checks the [pipApp] task did not reshow during transition. */ @Presubmit @Test fun app1WindowIsVisibleOnceApp2WindowIsInvisible() { flicker.assertLayers { this.isVisible(pipApp) - .then().isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) - .then().isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) - .then().isInvisible(pipApp).isVisible(secondaryApp) + .then() + .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isInvisible(pipApp) + .isVisible(secondaryApp) } } @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = - flicker.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, - landscapePosLeft = tapl.isTablet, - portraitPosTop = false - ) + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = - flicker.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, - landscapePosLeft = !tapl.isTablet, - portraitPosTop = true - ) + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) - /** - * Checks the [pipApp] task become invisible after transition finish. - */ - @Presubmit - @Test - fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp) + /** Checks the [pipApp] task become invisible after transition finish. */ + @Presubmit @Test fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp) - /** - * Checks the [pipApp] task is in split screen bounds when transition start. - */ + /** Checks the [pipApp] task is in split screen bounds when transition start. */ @Presubmit @Test fun pipAppBoundsIsVisibleAtBegin() = - flicker.assertLayersStart { - this.splitAppLayerBoundsSnapToDivider( - pipApp, - landscapePosLeft = !tapl.isTablet, - portraitPosTop = true, - flicker.scenario.startRotation - ) - } + flicker.assertLayersStart { + this.splitAppLayerBoundsSnapToDivider( + pipApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true, + flicker.scenario.startRotation + ) + } companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams() = LegacyFlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) - ) + ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index 10dceba3d680..4c4402803ae2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt @@ -69,11 +69,12 @@ class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) : @Test @Presubmit fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() = - flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( - LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf( - WALLPAPER_BBQ_WRAPPER + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + listOf(WALLPAPER_BBQ_WRAPPER) ) - ) } + } @Test fun splitScreenDividerIsVisibleAtEnd() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 1477cf7415cf..5d87cf8b25a6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -57,6 +57,7 @@ import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.After @@ -92,6 +93,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController + @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -276,8 +278,8 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN - controller.moveToDesktop(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) } @@ -286,15 +288,15 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() { val task = setUpFullscreenTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM - controller.moveToDesktop(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @Test fun moveToDesktop_nonExistentTask_doesNothing() { - controller.moveToDesktop(999) + controller.moveToDesktop(desktopModeWindowDecoration, 999) verifyWCTNotExecuted() } @@ -305,9 +307,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val fullscreenTask = setUpFullscreenTask() markTaskHidden(freeformTask) - controller.moveToDesktop(fullscreenTask) + controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestMoveToDesktopWct()) { // Operations should include home task, freeform task assertThat(hierarchyOps).hasSize(3) assertReorderSequence(homeTask, freeformTask, fullscreenTask) @@ -327,9 +329,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) markTaskHidden(freeformTaskSecond) - controller.moveToDesktop(fullscreenTaskDefault) + controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestMoveToDesktopWct()) { // Check that hierarchy operations do not include tasks from second display assertThat(hierarchyOps.map { it.container }) .doesNotContain(homeTaskSecond.token.asBinder()) @@ -498,6 +500,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) markTaskHidden(stashedFreeformTask) @@ -569,6 +572,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) markTaskHidden(stashedFreeformTask) @@ -626,6 +630,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun stashDesktopApps_stateUpdates() { + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + controller.stashDesktopApps(DEFAULT_DISPLAY) assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() @@ -634,6 +640,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun hideStashedDesktopApps_stateUpdates() { + whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) controller.hideStashedDesktopApps(DEFAULT_DISPLAY) @@ -715,6 +723,16 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun getLatestMoveToDesktopWct(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (ENABLE_SHELL_TRANSITIONS) { + verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any()) + } else { + verify(shellTaskOrganizer).applyTransaction(arg.capture()) + } + return arg.value + } + private fun verifyWCTNotExecuted() { if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index 50435a02a44d..d098d332a376 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -607,4 +607,29 @@ public class TaskViewTest extends ShellTestCase { verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded(); verify(mOrganizer).applyTransaction(any()); } + + @Test + public void testReleaseInOnTaskRemoval_noNPE() { + mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer, + mTaskViewTransitions, mSyncQueue)); + mTaskView = new TaskView(mContext, mTaskViewTaskController); + mTaskView.setListener(mExecutor, new TaskView.Listener() { + @Override + public void onTaskRemovalStarted(int taskId) { + mTaskView.release(); + } + }); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mTaskViewTaskController.prepareOpenAnimation(true /* newTask */, + new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo, + mLeash, wct); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + + assertThat(mTaskViewTaskController.getTaskInfo()).isEqualTo(mTaskInfo); + + mTaskViewTaskController.prepareCloseAnimation(); + + assertThat(mTaskViewTaskController.getTaskInfo()).isNull(); + } } diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java index 4c0850b675a8..4ad3cd1e3f1b 100644 --- a/media/java/android/media/AudioFocusRequest.java +++ b/media/java/android/media/AudioFocusRequest.java @@ -39,8 +39,8 @@ import android.os.Looper; * but there is only one the user would really listen to (focus on), while the other plays in * the background. An example of this is driving directions being spoken while music plays at * a reduced volume (a.k.a. ducking). - * <p>When an application requests audio focus, it expresses its intention to “own” audio focus to - * play audio. Let’s review the different types of focus requests, the return value after a request, + * <p>When an application requests audio focus, it expresses its intention to "own" audio focus to + * play audio. Let's review the different types of focus requests, the return value after a request, * and the responses to a loss. * <p class="note">Note: applications should not play anything until granted focus.</p> * @@ -51,7 +51,7 @@ import android.os.Looper; * <li>{@link AudioManager#AUDIOFOCUS_GAIN} expresses the fact that your application is now the * sole source of audio that the user is listening to. The duration of the audio playback is * unknown, and is possibly very long: after the user finishes interacting with your application, - * (s)he doesn’t expect another audio stream to resume. Examples of uses of this focus gain are + * (s)he doesn't expect another audio stream to resume. Examples of uses of this focus gain are * for music playback, for a game or a video player.</li> * * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT} is for a situation when you know your @@ -60,20 +60,20 @@ import android.os.Looper; * for playing an alarm, or during a VoIP call. The playback is known to be finite: the alarm will * time-out or be dismissed, the VoIP call has a beginning and an end. When any of those events * ends, and if the user was listening to music when it started, the user expects music to resume, - * but didn’t wish to listen to both at the same time.</li> + * but didn't wish to listen to both at the same time.</li> * * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}: this focus request type is similar * to {@code AUDIOFOCUS_GAIN_TRANSIENT} for the temporary aspect of the focus request, but it also * expresses the fact during the time you own focus, you allow another application to keep playing - * at a reduced volume, “ducked”. Examples are when playing driving directions or notifications, - * it’s ok for music to keep playing, but not loud enough that it would prevent the directions to - * be hard to understand. A typical attenuation by the “ducked” application is a factor of 0.2f + * at a reduced volume, "ducked". Examples are when playing driving directions or notifications, + * it's ok for music to keep playing, but not loud enough that it would prevent the directions to + * be hard to understand. A typical attenuation by the "ducked" application is a factor of 0.2f * (or -14dB), that can for instance be applied with {@code MediaPlayer.setVolume(0.2f)} when * using this class for playback.</li> * * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} is also for a temporary request, * but also expresses that your application expects the device to not play anything else. This is - * typically used if you are doing audio recording or speech recognition, and don’t want for + * typically used if you are doing audio recording or speech recognition, and don't want for * examples notifications to be played by the system during that time.</li> * </ul> * diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index b3f72a1aa2bb..73f15f21596c 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -30,12 +30,20 @@ interface IRingtonePlayer { /** Used for Ringtone.java playback */ @UnsupportedAppUsage oneway void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping); + oneway void stop(IBinder token); + boolean isPlaying(IBinder token); + + // RingtoneV1 + oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa, + float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig); + oneway void setPlaybackProperties(IBinder token, float volume, boolean looping, + boolean hapticGeneratorEnabled); + + // RingtoneV2 oneway void playRemoteRingtone(IBinder token, in Uri uri, in AudioAttributes aa, boolean useExactAudioAttributes, int enabledMedia, in @nullable VibrationEffect ve, float volume, boolean looping, boolean hapticGeneratorEnabled, in @nullable VolumeShaper.Configuration volumeShaperConfig); - oneway void stop(IBinder token); - boolean isPlaying(IBinder token); oneway void setLooping(IBinder token, boolean looping); oneway void setVolume(IBinder token, float volume); oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled); diff --git a/media/java/android/media/LocalRingtonePlayer.java b/media/java/android/media/LocalRingtonePlayer.java index d0169b9e2501..fe7cc3ec2af3 100644 --- a/media/java/android/media/LocalRingtonePlayer.java +++ b/media/java/android/media/LocalRingtonePlayer.java @@ -37,7 +37,7 @@ import java.util.Objects; * @hide */ public class LocalRingtonePlayer - implements Ringtone.RingtonePlayer, MediaPlayer.OnCompletionListener { + implements RingtoneV2.RingtonePlayer, MediaPlayer.OnCompletionListener { private static final String TAG = "LocalRingtonePlayer"; // keep references on active Ringtones until stopped or completion listener called. @@ -45,7 +45,7 @@ public class LocalRingtonePlayer private final MediaPlayer mMediaPlayer; private final AudioAttributes mAudioAttributes; - private final Ringtone.RingtonePlayer mVibrationPlayer; + private final RingtoneV2.RingtonePlayer mVibrationPlayer; private final Ringtone.Injectables mInjectables; private final AudioManager mAudioManager; private final VolumeShaper mVolumeShaper; @@ -55,7 +55,7 @@ public class LocalRingtonePlayer @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables, @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator, @Nullable VolumeShaper volumeShaper, - @Nullable Ringtone.RingtonePlayer vibrationPlayer) { + @Nullable RingtoneV2.RingtonePlayer vibrationPlayer) { Objects.requireNonNull(mediaPlayer); Objects.requireNonNull(audioAttributes); Objects.requireNonNull(injectables); @@ -74,7 +74,7 @@ public class LocalRingtonePlayer * loaded in the local player. */ @Nullable - static Ringtone.RingtonePlayer create(@NonNull Context context, + static RingtoneV2.RingtonePlayer create(@NonNull Context context, @NonNull AudioManager audioManager, @NonNull Vibrator vibrator, @NonNull Uri soundUri, @NonNull AudioAttributes audioAttributes, @@ -311,7 +311,7 @@ public class LocalRingtonePlayer } /** A RingtonePlayer that only plays a VibrationEffect. */ - static class VibrationEffectPlayer implements Ringtone.RingtonePlayer { + static class VibrationEffectPlayer implements RingtoneV2.RingtonePlayer { private static final int VIBRATION_LOOP_DELAY_MS = 200; private final VibrationEffect mVibrationEffect; private final VibrationAttributes mVibrationAttributes; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 4323c738f910..1ee5aa36740c 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1057,7 +1057,7 @@ public class MediaPlayer extends PlayerBase * this API to pass the cookies as a list of HttpCookie. If the app has not installed * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with * the provided cookies. If the app has installed its own handler already, this API requires the - * handler to be of CookieManager type such that the API can update the manager’s CookieStore. + * handler to be of CookieManager type such that the API can update the manager's CookieStore. * * <p><strong>Note</strong> that the cross domain redirection is allowed by default, * but that can be changed with key/value pairs through the headers parameter with diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index ed5226d860fe..ca1c2f9ec633 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -424,14 +424,7 @@ public final class MediaRouter2 { * call is ignored because the app is in the background. */ public boolean showSystemOutputSwitcher() { - synchronized (mLock) { - try { - return mMediaRouterService.showMediaOutputSwitcher(mImpl.getPackageName()); - } catch (RemoteException ex) { - ex.rethrowFromSystemServer(); - } - } - return false; + return mImpl.showSystemOutputSwitcher(); } /** @@ -1945,6 +1938,8 @@ public final class MediaRouter2 { void setRouteListingPreference(@Nullable RouteListingPreference preference); + boolean showSystemOutputSwitcher(); + List<MediaRoute2Info> getAllRoutes(); void setOnGetControllerHintsListener(OnGetControllerHintsListener listener); @@ -2091,6 +2086,12 @@ public final class MediaRouter2 { "RouteListingPreference cannot be set by a privileged MediaRouter2 instance."); } + @Override + public boolean showSystemOutputSwitcher() { + throw new UnsupportedOperationException( + "Cannot show system output switcher from a privileged router."); + } + /** Gets the list of all discovered routes. */ @Override public List<MediaRoute2Info> getAllRoutes() { @@ -2903,6 +2904,18 @@ public final class MediaRouter2 { } } + @Override + public boolean showSystemOutputSwitcher() { + synchronized (mLock) { + try { + return mMediaRouterService.showMediaOutputSwitcher(mImpl.getPackageName()); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } + return false; + } + /** * Returns {@link Collections#emptyList()}. Local routes can only access routes related to * their {@link RouteDiscoveryPreference} through {@link #getRoutes()}. diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 3a6b39834851..0319f32521c1 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -25,14 +25,11 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; -import android.content.res.AssetFileDescriptor; -import android.content.res.Resources.NotFoundException; import android.database.Cursor; import android.media.audiofx.HapticGenerator; import android.net.Uri; -import android.os.Binder; -import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.Trace; import android.os.VibrationEffect; import android.os.Vibrator; @@ -43,7 +40,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -100,68 +96,70 @@ public class Ringtone { private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR " + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')"; - private final Context mContext; - private final Vibrator mVibrator; - private final AudioManager mAudioManager; - private VolumeShaper.Configuration mVolumeShaperConfig; + // Flag-selected ringtone implementation to use. + private final ApiInterface mApiImpl; + + /** {@hide} */ + @UnsupportedAppUsage + public Ringtone(Context context, boolean allowRemote) { + mApiImpl = new RingtoneV1(context, allowRemote); + } /** - * Flag indicating if we're allowed to fall back to remote playback using - * {@link #mRemoteRingtoneService}. Typically this is false when we're the remote - * player and there is nobody else to delegate to. + * Constructor for legacy V1 initialization paths using non-public APIs on RingtoneV1. */ - private final boolean mAllowRemote; - private final IRingtonePlayer mRemoteRingtoneService; - private final Injectables mInjectables; - - private final int mEnabledMedia; - - private final Uri mUri; - private String mTitle; - - private AudioAttributes mAudioAttributes; - private boolean mUseExactAudioAttributes; - private boolean mPreferBuiltinDevice; - private RingtonePlayer mActivePlayer; - // playback properties, use synchronized with mPlaybackSettingsLock - private boolean mIsLooping; - private float mVolume; - private boolean mHapticGeneratorEnabled; - private final Object mPlaybackSettingsLock = new Object(); - private final VibrationEffect mVibrationEffect; + private Ringtone(RingtoneV1 ringtoneV1) { + mApiImpl = ringtoneV1; + } private Ringtone(Builder builder, @Ringtone.RingtoneMedia int effectiveEnabledMedia, @NonNull AudioAttributes effectiveAudioAttributes, @Nullable VibrationEffect effectiveVibrationEffect, boolean effectiveHapticGeneratorEnabled) { - // Context - mContext = builder.mContext; - mInjectables = builder.mInjectables; - //mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - mAudioManager = mContext.getSystemService(AudioManager.class); - mRemoteRingtoneService = builder.mAllowRemote ? mAudioManager.getRingtonePlayer() : null; - mVibrator = mContext.getSystemService(Vibrator.class); - - // Local-only (not propagated to remote). - mPreferBuiltinDevice = builder.mPreferBuiltinDevice; // System-only - mAllowRemote = (mRemoteRingtoneService != null); // Always false for remote. - - // Properties potentially propagated to remote player. - mEnabledMedia = effectiveEnabledMedia; - mUri = builder.mUri; - mVolumeShaperConfig = builder.mVolumeShaperConfig; - mVolume = builder.mInitialSoundVolume; - mIsLooping = builder.mLooping; - mVibrationEffect = effectiveVibrationEffect; - mAudioAttributes = effectiveAudioAttributes; - mUseExactAudioAttributes = builder.mUseExactAudioAttributes; - mHapticGeneratorEnabled = effectiveHapticGeneratorEnabled; + mApiImpl = new RingtoneV2(builder.mContext, builder.mInjectables, builder.mAllowRemote, + effectiveEnabledMedia, builder.mUri, effectiveAudioAttributes, + builder.mUseExactAudioAttributes, builder.mVolumeShaperConfig, + builder.mPreferBuiltinDevice, builder.mInitialSoundVolume, builder.mLooping, + effectiveHapticGeneratorEnabled, effectiveVibrationEffect); + } + + /** + * Temporary V1 constructor for legacy V1 paths with audio attributes. + * @hide + */ + public static Ringtone createV1WithCustomAudioAttributes( + Context context, AudioAttributes audioAttributes, Uri uri, + VolumeShaper.Configuration volumeShaperConfig, boolean allowRemote) { + RingtoneV1 ringtoneV1 = new RingtoneV1(context, allowRemote); + ringtoneV1.setAudioAttributesField(audioAttributes); + ringtoneV1.setUri(uri, volumeShaperConfig); + ringtoneV1.reinitializeActivePlayer(); + return new Ringtone(ringtoneV1); + } + + /** + * Temporary V1 constructor for legacy V1 paths with stream type. + * @hide + */ + public static Ringtone createV1WithCustomStreamType( + Context context, int streamType, Uri uri, + VolumeShaper.Configuration volumeShaperConfig) { + RingtoneV1 ringtoneV1 = new RingtoneV1(context, /* allowRemote= */ true); + if (streamType >= 0) { + ringtoneV1.setStreamType(streamType); + } + ringtoneV1.setUri(uri, volumeShaperConfig); + if (!ringtoneV1.reinitializeActivePlayer()) { + Log.e(TAG, "Failed to open ringtone " + uri); + return null; + } + return new Ringtone(ringtoneV1); } /** @hide */ @RingtoneMedia public int getEnabledMedia() { - return mEnabledMedia; + return mApiImpl.getEnabledMedia(); } /** @@ -172,15 +170,7 @@ public class Ringtone { */ @Deprecated public void setStreamType(int streamType) { - setAudioAttributes( - getAudioAttributesForLegacyStreamType(streamType, "setStreamType()")); - } - - private AudioAttributes getAudioAttributesForLegacyStreamType(int streamType, String originOp) { - PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", originOp); - return new AudioAttributes.Builder() - .setInternalLegacyStreamType(streamType) - .build(); + mApiImpl.setStreamType(streamType); } /** @@ -192,7 +182,7 @@ public class Ringtone { */ @Deprecated public int getStreamType() { - return AudioAttributes.toLegacyStreamType(mAudioAttributes); + return mApiImpl.getStreamType(); } /** @@ -201,17 +191,7 @@ public class Ringtone { */ public void setAudioAttributes(AudioAttributes attributes) throws IllegalArgumentException { - // TODO: deprecate this method - it will be done with a builder. - if (attributes == null) { - throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone"); - } - mAudioAttributes = attributes; - // Setting the audio attributes requires re-initializing the player. - if (mActivePlayer != null) { - // The audio attributes have to be set before the media player is prepared. - // Re-initialize it. - reinitializeActivePlayer(); - } + mApiImpl.setAudioAttributes(attributes); } /** @@ -221,19 +201,19 @@ public class Ringtone { */ @Nullable public VibrationEffect getVibrationEffect() { - return mVibrationEffect; + return mApiImpl.getVibrationEffect(); } /** @hide */ @VisibleForTesting public boolean getPreferBuiltinDevice() { - return mPreferBuiltinDevice; + return mApiImpl.getPreferBuiltinDevice(); } /** @hide */ @VisibleForTesting public VolumeShaper.Configuration getVolumeShaperConfig() { - return mVolumeShaperConfig; + return mApiImpl.getVolumeShaperConfig(); } /** @@ -243,31 +223,13 @@ public class Ringtone { */ @VisibleForTesting public boolean isLocalOnly() { - return !mAllowRemote; + return mApiImpl.isLocalOnly(); } /** @hide */ @VisibleForTesting public boolean isUsingRemotePlayer() { - return mActivePlayer instanceof RemoteRingtonePlayer; - } - - /** - * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is - * the one on which outgoing audio for SIM calls is played. - * - * @param audioManager the audio manage. - * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if - * none can be found. - */ - private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) { - AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); - for (AudioDeviceInfo device : deviceList) { - if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { - return device; - } - } - return null; + return mApiImpl.isUsingRemotePlayer(); } /** @@ -277,84 +239,7 @@ public class Ringtone { * @hide */ public boolean reinitializeActivePlayer() { - // Try creating a local media player, or fallback to creating a remote one. - Trace.beginSection("reinitializeActivePlayer"); - try { - if (mActivePlayer != null) { - // This would only happen if calling the deprecated setAudioAttributes after - // building the Ringtone. - stopAndReleaseActivePlayer(); - } - - boolean vibrationOnly = (mEnabledMedia & MEDIA_ALL) == MEDIA_VIBRATION; - // Vibration can come from the audio file if using haptic generator or if haptic - // channels are a possibility. - boolean maybeAudioVibration = mUri != null && mInjectables.isHapticPlaybackSupported() - && (mHapticGeneratorEnabled || !mAudioAttributes.areHapticChannelsMuted()); - - // VibrationEffect only, use the simplified player without checking for haptic channels. - if (vibrationOnly && !maybeAudioVibration && mVibrationEffect != null) { - mActivePlayer = new LocalRingtonePlayer.VibrationEffectPlayer( - mVibrationEffect, mAudioAttributes, mVibrator, mIsLooping); - return true; - } - - AudioDeviceInfo preferredDevice = - mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null; - if (mUri != null) { - mActivePlayer = LocalRingtonePlayer.create(mContext, mAudioManager, mVibrator, mUri, - mAudioAttributes, vibrationOnly, mVibrationEffect, mInjectables, - mVolumeShaperConfig, preferredDevice, mHapticGeneratorEnabled, mIsLooping, - mVolume); - } else { - // Using the remote player won't help play a null Uri. Revert straight to fallback. - // The vibration-only case was already covered above. - mActivePlayer = createFallbackRingtonePlayer(); - // Fall through to attempting remote fallback play if null. - } - - if (mActivePlayer == null && mAllowRemote) { - mActivePlayer = new RemoteRingtonePlayer(mRemoteRingtoneService, mUri, - mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect, - mVolumeShaperConfig, mHapticGeneratorEnabled, mIsLooping, mVolume); - } - - return mActivePlayer != null; - } finally { - Trace.endSection(); - } - } - - @Nullable - private LocalRingtonePlayer createFallbackRingtonePlayer() { - int ringtoneType = RingtoneManager.getDefaultType(mUri); - if (ringtoneType != -1 - && RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) { - Log.w(TAG, "not playing fallback for " + mUri); - return null; - } - // Default ringtone, try fallback ringtone. - try (AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( - com.android.internal.R.raw.fallbackring)) { - if (afd == null) { - Log.e(TAG, "Could not load fallback ringtone"); - return null; - } - - AudioDeviceInfo preferredDevice = - mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null; - return LocalRingtonePlayer.createForFallback(mAudioManager, mVibrator, afd, - mAudioAttributes, mVibrationEffect, mInjectables, mVolumeShaperConfig, - preferredDevice, mIsLooping, mVolume); - } catch (NotFoundException nfe) { - Log.e(TAG, "Fallback ringtone does not exist"); - return null; - } catch (IOException e) { - // As with the above messages, not including much information about the - // failure so as not to expose details of the fallback ringtone resource. - Log.e(TAG, "Exception reading fallback ringtone"); - return null; - } + return mApiImpl.reinitializeActivePlayer(); } /** @@ -362,7 +247,7 @@ public class Ringtone { * @hide */ public boolean hasHapticChannels() { - return (mActivePlayer == null) ? false : mActivePlayer.hasHapticChannels(); + return mApiImpl.hasHapticChannels(); } /** @@ -371,7 +256,7 @@ public class Ringtone { * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. */ public AudioAttributes getAudioAttributes() { - return mAudioAttributes; + return mApiImpl.getAudioAttributes(); } /** @@ -379,12 +264,7 @@ public class Ringtone { * @param looping whether to loop or not. */ public void setLooping(boolean looping) { - synchronized (mPlaybackSettingsLock) { - mIsLooping = looping; - if (mActivePlayer != null) { - mActivePlayer.setLooping(looping); - } - } + mApiImpl.setLooping(looping); } /** @@ -392,9 +272,7 @@ public class Ringtone { * @return true if this player loops when playing. */ public boolean isLooping() { - synchronized (mPlaybackSettingsLock) { - return mIsLooping; - } + return mApiImpl.isLooping(); } /** @@ -403,22 +281,7 @@ public class Ringtone { * corresponds to no attenuation being applied. */ public void setVolume(float volume) { - // Ignore if sound not enabled. - if ((mEnabledMedia & MEDIA_SOUND) == 0) { - return; - } - if (volume < 0.0f) { - volume = 0.0f; - } else if (volume > 1.0f) { - volume = 1.0f; - } - - synchronized (mPlaybackSettingsLock) { - mVolume = volume; - if (mActivePlayer != null) { - mActivePlayer.setVolume(volume); - } - } + mApiImpl.setVolume(volume); } /** @@ -426,9 +289,7 @@ public class Ringtone { * @return a value between 0.0f and 1.0f. */ public float getVolume() { - synchronized (mPlaybackSettingsLock) { - return mVolume; - } + return mApiImpl.getVolume(); } /** @@ -439,16 +300,7 @@ public class Ringtone { * @see android.media.audiofx.HapticGenerator#isAvailable() */ public boolean setHapticGeneratorEnabled(boolean enabled) { - if (!mInjectables.isHapticGeneratorAvailable()) { - return false; - } - synchronized (mPlaybackSettingsLock) { - mHapticGeneratorEnabled = enabled; - if (mActivePlayer != null) { - mActivePlayer.setHapticGeneratorEnabled(enabled); - } - } - return true; + return mApiImpl.setHapticGeneratorEnabled(enabled); } /** @@ -456,9 +308,7 @@ public class Ringtone { * @return true if the HapticGenerator is enabled. */ public boolean isHapticGeneratorEnabled() { - synchronized (mPlaybackSettingsLock) { - return mHapticGeneratorEnabled; - } + return mApiImpl.isHapticGeneratorEnabled(); } /** @@ -468,8 +318,7 @@ public class Ringtone { * @param context A context used for querying. */ public String getTitle(Context context) { - if (mTitle != null) return mTitle; - return mTitle = getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote); + return mApiImpl.getTitle(context); } /** @@ -546,38 +395,21 @@ public class Ringtone { /** {@hide} */ @UnsupportedAppUsage public Uri getUri() { - return mUri; + return mApiImpl.getUri(); } /** * Plays the ringtone. */ public void play() { - if (mActivePlayer != null) { - if (mActivePlayer.play()) { - return; - } else { - // Discard active player: play() is only meant to be called once. - stopAndReleaseActivePlayer(); - } - } - if (!playFallbackRingtone()) { - Log.w(TAG, "Neither local nor remote playback available"); - } + mApiImpl.play(); } /** * Stops a playing ringtone. */ public void stop() { - stopAndReleaseActivePlayer(); - } - - private void stopAndReleaseActivePlayer() { - if (mActivePlayer != null) { - mActivePlayer.stopAndRelease(); - mActivePlayer = null; - } + mApiImpl.stop(); } /** @@ -586,41 +418,7 @@ public class Ringtone { * @return True if playing, false otherwise. */ public boolean isPlaying() { - if (mActivePlayer != null) { - return mActivePlayer.isPlaying(); - } else { - Log.w(TAG, "No active ringtone player"); - return false; - } - } - - /** - * Fallback during the play stage rather than initialization, typically due to an issue - * communicating with the remote player. - */ - private boolean playFallbackRingtone() { - if (mActivePlayer != null) { - Log.wtf(TAG, "Playing fallback ringtone with another active player"); - stopAndReleaseActivePlayer(); - } - int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes); - if (mAudioManager.getStreamVolume(streamType) == 0) { - // TODO: Return true? If volume is off, this is a successful play. - return false; - } - mActivePlayer = createFallbackRingtonePlayer(); - if (mActivePlayer == null) { - return false; // the create method logs if it returns null. - } else if (mActivePlayer.play()) { - return true; - } else { - stopAndReleaseActivePlayer(); - return false; - } - } - - void setTitle(String title) { - mTitle = title; + return mApiImpl.isPlaying(); } /** @@ -887,140 +685,6 @@ public class Ringtone { } /** - * Play a specific ringtone. This interface is implemented by either local (this process) or - * proxied-remote playback via AudioManager.getRingtonePlayer, so that the caller - * (Ringtone class) can just use a single player after the initial creation. - * @hide - */ - interface RingtonePlayer { - /** - * Start playing the ringtone, returning false if there was a problem that - * requires falling back to the fallback ringtone resource. - */ - boolean play(); - boolean isPlaying(); - void stopAndRelease(); - - // Mutating playback methods. - void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo); - void setLooping(boolean looping); - void setHapticGeneratorEnabled(boolean enabled); - void setVolume(float volume); - - boolean hasHapticChannels(); - } - - /** - * Remote RingtonePlayer. All operations are delegated via the IRingtonePlayer interface, which - * should ultimately be backed by a RingtoneLocalPlayer within the system services. - */ - static class RemoteRingtonePlayer implements RingtonePlayer { - private final IBinder mRemoteToken = new Binder(); - private final IRingtonePlayer mRemoteRingtoneService; - private final Uri mCanonicalUri; - private final int mEnabledMedia; - private final VibrationEffect mVibrationEffect; - private final VolumeShaper.Configuration mVolumeShaperConfig; - private final AudioAttributes mAudioAttributes; - private final boolean mUseExactAudioAttributes; - private boolean mIsLooping; - private float mVolume; - private boolean mHapticGeneratorEnabled; - - RemoteRingtonePlayer(@NonNull IRingtonePlayer remoteRingtoneService, - @NonNull Uri uri, @NonNull AudioAttributes audioAttributes, - boolean useExactAudioAttributes, - @RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect, - @Nullable VolumeShaper.Configuration volumeShaperConfig, - boolean hapticGeneratorEnabled, boolean initialIsLooping, float initialVolume) { - mRemoteRingtoneService = remoteRingtoneService; - mCanonicalUri = (uri == null) ? null : uri.getCanonicalUri(); - mAudioAttributes = audioAttributes; - mUseExactAudioAttributes = useExactAudioAttributes; - mEnabledMedia = enabledMedia; - mVibrationEffect = vibrationEffect; - mVolumeShaperConfig = volumeShaperConfig; - mHapticGeneratorEnabled = hapticGeneratorEnabled; - mIsLooping = initialIsLooping; - mVolume = initialVolume; - } - - @Override - public boolean play() { - try { - mRemoteRingtoneService.playRemoteRingtone(mRemoteToken, mCanonicalUri, - mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect, - mVolume, mIsLooping, mHapticGeneratorEnabled, mVolumeShaperConfig); - return true; - } catch (RemoteException e) { - Log.w(TAG, "Problem playing ringtone: " + e); - return false; - } - } - - @Override - public boolean isPlaying() { - try { - return mRemoteRingtoneService.isPlaying(mRemoteToken); - } catch (RemoteException e) { - Log.w(TAG, "Problem checking ringtone isPlaying: " + e); - return false; - } - } - - @Override - public void stopAndRelease() { - try { - mRemoteRingtoneService.stop(mRemoteToken); - } catch (RemoteException e) { - Log.w(TAG, "Problem stopping ringtone: " + e); - } - } - - @Override - public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { - // un-implemented for remote (but not used outside system). - } - - @Override - public void setLooping(boolean looping) { - mIsLooping = looping; - try { - mRemoteRingtoneService.setLooping(mRemoteToken, looping); - } catch (RemoteException e) { - Log.w(TAG, "Problem setting looping: " + e); - } - } - - @Override - public void setHapticGeneratorEnabled(boolean enabled) { - mHapticGeneratorEnabled = enabled; - try { - mRemoteRingtoneService.setHapticGeneratorEnabled(mRemoteToken, enabled); - } catch (RemoteException e) { - Log.w(TAG, "Problem setting hapticGeneratorEnabled: " + e); - } - } - - @Override - public void setVolume(float volume) { - mVolume = volume; - try { - mRemoteRingtoneService.setVolume(mRemoteToken, volume); - } catch (RemoteException e) { - Log.w(TAG, "Problem setting volume: " + e); - } - } - - @Override - public boolean hasHapticChannels() { - // FIXME: support remote player, or internalize haptic channels support and remove - // entirely. - return false; - } - } - - /** * Interface for intercepting static methods and constructors, for unit testing only. * @hide */ @@ -1071,4 +735,47 @@ public class Ringtone { } } + + /** + * Interface for alternative Ringtone implementations. See the public Ringtone methods that + * delegate to these for documentation. + * @hide + */ + interface ApiInterface { + void setStreamType(int streamType); + int getStreamType(); + void setAudioAttributes(AudioAttributes attributes); + boolean getPreferBuiltinDevice(); + VolumeShaper.Configuration getVolumeShaperConfig(); + boolean isLocalOnly(); + boolean isUsingRemotePlayer(); + boolean reinitializeActivePlayer(); + boolean hasHapticChannels(); + AudioAttributes getAudioAttributes(); + void setLooping(boolean looping); + boolean isLooping(); + void setVolume(float volume); + float getVolume(); + boolean setHapticGeneratorEnabled(boolean enabled); + boolean isHapticGeneratorEnabled(); + String getTitle(Context context); + Uri getUri(); + void play(); + void stop(); + boolean isPlaying(); + // V2 future-public methods + @RingtoneMedia int getEnabledMedia(); + VibrationEffect getVibrationEffect(); + } + + /** + * Switch for using the new ringtone implementation (RingtoneV1 vs RingtoneV2). This may be + * called from both system server and app-side sdk. + * + * @hide + */ + public static boolean useRingtoneV2() { + // TODO(b/293846645): chang eto new flagging infra + return SystemProperties.getBoolean("persist.audio.ringtone.use_v2", false); + } } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 12766fbb6c9d..0ad8c24b10c6 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -496,13 +496,32 @@ public class RingtoneManager { mPreviousRingtone.stop(); } - mPreviousRingtone = new Ringtone.Builder( - mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType)) - .setUri(getRingtoneUri(position)) - .build(); + Ringtone ringtone; + Uri positionUri = getRingtoneUri(position); + if (Ringtone.useRingtoneV2()) { + mPreviousRingtone = new Ringtone.Builder( + mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType)) + .setUri(positionUri) + .build(); + } else { + mPreviousRingtone = createRingtoneV1WithStreamType(mContext, positionUri, + inferStreamType(), /* volumeShaperConfig= */ null); + } return mPreviousRingtone; } + private static Ringtone createRingtoneV1WithStreamType( + final Context context, Uri ringtoneUri, int streamType, + @Nullable VolumeShaper.Configuration volumeShaperConfig) { + try { + return Ringtone.createV1WithCustomStreamType(context, streamType, ringtoneUri, + volumeShaperConfig); + } catch (Exception ex) { + Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex); + } + return null; + } + /** * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}. * @@ -694,9 +713,14 @@ public class RingtoneManager { * @return A {@link Ringtone} for the given URI, or null. */ public static Ringtone getRingtone(final Context context, Uri ringtoneUri) { - return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(-1)) - .setUri(ringtoneUri) - .build(); + if (Ringtone.useRingtoneV2()) { + return new Ringtone.Builder( + context, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(-1)) + .setUri(ringtoneUri) + .build(); + } else { + return createRingtoneV1WithStreamType(context, ringtoneUri, -1, null); + } } /** @@ -706,11 +730,22 @@ public class RingtoneManager { @Nullable VolumeShaper.Configuration volumeShaperConfig, AudioAttributes audioAttributes) { // TODO: move caller(s) away from this method: inline the builder call. - return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes) - .setUri(ringtoneUri) - .setVolumeShaperConfig(volumeShaperConfig) - .setUseExactAudioAttributes(true) // May be using audio-coupled via attrs - .build(); + if (Ringtone.useRingtoneV2()) { + return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes) + .setUri(ringtoneUri) + .setVolumeShaperConfig(volumeShaperConfig) + .setUseExactAudioAttributes(true) // May be using audio-coupled via attrs + .build(); + } else { + try { + return Ringtone.createV1WithCustomAudioAttributes(context, audioAttributes, + ringtoneUri, volumeShaperConfig, /* allowRemote= */ true); + } catch (Exception ex) { + // Match broad catching of createRingtoneV1. + Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex); + return null; + } + } } /** diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java new file mode 100644 index 000000000000..3c54d4a0d166 --- /dev/null +++ b/media/java/android/media/RingtoneV1.java @@ -0,0 +1,614 @@ +/* + * 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 android.media; + +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; +import android.provider.MediaStore; +import android.provider.MediaStore.MediaColumns; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * Hosts original Ringtone implementation, retained for flagging large builder+vibration features + * in RingtoneV2.java. This does not support new features in the V2 builder. + * + * Only modified methods are moved here. + * + * @hide + */ +class RingtoneV1 implements Ringtone.ApiInterface { + private static final String TAG = "RingtoneV1"; + private static final boolean LOGD = true; + + private static final String[] MEDIA_COLUMNS = new String[] { + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.TITLE + }; + /** Selection that limits query results to just audio files */ + private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR " + + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')"; + + // keep references on active Ringtones until stopped or completion listener called. + private static final ArrayList<RingtoneV1> sActiveRingtones = new ArrayList<>(); + + private final Context mContext; + private final AudioManager mAudioManager; + private VolumeShaper.Configuration mVolumeShaperConfig; + private VolumeShaper mVolumeShaper; + + /** + * Flag indicating if we're allowed to fall back to remote playback using + * {@link #mRemotePlayer}. Typically this is false when we're the remote + * player and there is nobody else to delegate to. + */ + private final boolean mAllowRemote; + 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; + + private AudioAttributes mAudioAttributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(); + private boolean mPreferBuiltinDevice; + // playback properties, use synchronized with mPlaybackSettingsLock + private boolean mIsLooping = false; + private float mVolume = 1.0f; + private boolean mHapticGeneratorEnabled = false; + private final Object mPlaybackSettingsLock = new Object(); + + /** {@hide} */ + @UnsupportedAppUsage + public RingtoneV1(Context context, boolean allowRemote) { + mContext = context; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mAllowRemote = allowRemote; + mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null; + mRemoteToken = allowRemote ? new Binder() : null; + } + + /** + * Sets the stream type where this ringtone will be played. + * + * @param streamType The stream, see {@link AudioManager}. + * @deprecated use {@link #setAudioAttributes(AudioAttributes)} + */ + @Deprecated + public void setStreamType(int streamType) { + PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()"); + setAudioAttributes(new AudioAttributes.Builder() + .setInternalLegacyStreamType(streamType) + .build()); + } + + /** + * Gets the stream type where this ringtone will be played. + * + * @return The stream type, see {@link AudioManager}. + * @deprecated use of stream types is deprecated, see + * {@link #setAudioAttributes(AudioAttributes)} + */ + @Deprecated + public int getStreamType() { + return AudioAttributes.toLegacyStreamType(mAudioAttributes); + } + + /** + * Sets the {@link AudioAttributes} for this ringtone. + * @param attributes the non-null attributes characterizing this ringtone. + */ + public void setAudioAttributes(AudioAttributes attributes) + throws IllegalArgumentException { + setAudioAttributesField(attributes); + // The audio attributes have to be set before the media player is prepared. + // Re-initialize it. + setUri(mUri, mVolumeShaperConfig); + reinitializeActivePlayer(); + } + + /** + * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create + * the media player. + * @hide + */ + public void setAudioAttributesField(@Nullable AudioAttributes attributes) { + if (attributes == null) { + throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone"); + } + mAudioAttributes = attributes; + } + + /** + * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is + * the one on which outgoing audio for SIM calls is played. + * + * @param audioManager the audio manage. + * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if + * none can be found. + */ + private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) { + AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : deviceList) { + if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + return device; + } + } + return null; + } + + /** + * Sets the preferred device of the ringtong playback to the built-in device. + * + * @hide + */ + public boolean preferBuiltinDevice(boolean enable) { + mPreferBuiltinDevice = enable; + if (mLocalPlayer == null) { + return true; + } + return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager)); + } + + /** + * Creates a local media player for the ringtone using currently set attributes. + * @return true if media player creation succeeded or is deferred, + * false if it did not succeed and can't be tried remotely. + * @hide + */ + public boolean reinitializeActivePlayer() { + Trace.beginSection("reinitializeActivePlayer"); + if (mUri == null) { + Log.e(TAG, "Could not create media player as no URI was provided."); + return mAllowRemote && mRemotePlayer != null; + } + destroyLocalPlayer(); + // try opening uri locally before delegating to remote player + mLocalPlayer = new MediaPlayer(); + try { + mLocalPlayer.setDataSource(mContext, mUri); + mLocalPlayer.setAudioAttributes(mAudioAttributes); + mLocalPlayer.setPreferredDevice( + mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null); + synchronized (mPlaybackSettingsLock) { + applyPlaybackProperties_sync(); + } + if (mVolumeShaperConfig != null) { + mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); + } + mLocalPlayer.prepare(); + + } catch (SecurityException | IOException e) { + destroyLocalPlayer(); + if (!mAllowRemote) { + Log.w(TAG, "Remote playback not allowed: " + e); + } + } + + if (LOGD) { + if (mLocalPlayer != null) { + Log.d(TAG, "Successfully created local player"); + } else { + Log.d(TAG, "Problem opening; delegating to remote player"); + } + } + Trace.endSection(); + return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null); + } + + /** + * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone. + * If the ringtone has not been created, it will load based on URI provided at {@link #setUri} + * and if not URI has been set, it will assume no haptic channels are present. + * @hide + */ + 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; + } + + /** + * Returns whether a local player has been created for this ringtone. + * @hide + */ + @VisibleForTesting + public boolean hasLocalPlayer() { + return mLocalPlayer != null; + } + + public @Ringtone.RingtoneMedia int getEnabledMedia() { + return Ringtone.MEDIA_SOUND; // RingtoneV2 only + } + + public VibrationEffect getVibrationEffect() { + return null; // RingtoneV2 only + } + + /** + * Returns the {@link AudioAttributes} used by this object. + * @return the {@link AudioAttributes} that were set with + * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. + */ + public AudioAttributes getAudioAttributes() { + return mAudioAttributes; + } + + /** + * Sets the player to be looping or non-looping. + * @param looping whether to loop or not. + */ + public void setLooping(boolean looping) { + synchronized (mPlaybackSettingsLock) { + mIsLooping = looping; + applyPlaybackProperties_sync(); + } + } + + /** + * Returns whether the looping mode was enabled on this player. + * @return true if this player loops when playing. + */ + public boolean isLooping() { + synchronized (mPlaybackSettingsLock) { + return mIsLooping; + } + } + + /** + * Sets the volume on this player. + * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0 + * corresponds to no attenuation being applied. + */ + public void setVolume(float volume) { + synchronized (mPlaybackSettingsLock) { + if (volume < 0.0f) { volume = 0.0f; } + if (volume > 1.0f) { volume = 1.0f; } + mVolume = volume; + applyPlaybackProperties_sync(); + } + } + + /** + * Returns the volume scalar set on this player. + * @return a value between 0.0f and 1.0f. + */ + public float getVolume() { + synchronized (mPlaybackSettingsLock) { + return mVolume; + } + } + + /** + * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can + * only be enabled on devices that support the effect. + * + * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false. + * @see android.media.audiofx.HapticGenerator#isAvailable() + */ + public boolean setHapticGeneratorEnabled(boolean enabled) { + if (!HapticGenerator.isAvailable()) { + return false; + } + synchronized (mPlaybackSettingsLock) { + mHapticGeneratorEnabled = enabled; + applyPlaybackProperties_sync(); + } + return true; + } + + /** + * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not. + * @return true if the HapticGenerator is enabled. + */ + public boolean isHapticGeneratorEnabled() { + synchronized (mPlaybackSettingsLock) { + return mHapticGeneratorEnabled; + } + } + + /** + * Must be called synchronized on mPlaybackSettingsLock + */ + private void applyPlaybackProperties_sync() { + if (mLocalPlayer != null) { + mLocalPlayer.setVolume(mVolume); + mLocalPlayer.setLooping(mIsLooping); + if (mHapticGenerator == null && mHapticGeneratorEnabled) { + mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId()); + } + if (mHapticGenerator != null) { + mHapticGenerator.setEnabled(mHapticGeneratorEnabled); + } + } else if (mAllowRemote && (mRemotePlayer != null)) { + try { + mRemotePlayer.setPlaybackProperties( + mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled); + } catch (RemoteException e) { + Log.w(TAG, "Problem setting playback properties: ", e); + } + } else { + Log.w(TAG, + "Neither local nor remote player available when applying playback properties"); + } + } + + /** + * Returns a human-presentable title for ringtone. Looks in media + * content provider. If not in either, uses the filename + * + * @param context A context used for querying. + */ + public String getTitle(Context context) { + if (mTitle != null) return mTitle; + return mTitle = Ringtone.getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote); + } + + /** + * Set {@link Uri} to be used for ringtone playback. + * {@link IRingtonePlayer}. + * + * @hide + */ + @UnsupportedAppUsage + public void setUri(Uri uri) { + setUri(uri, null); + } + + /** + * @hide + */ + public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) { + mVolumeShaperConfig = volumeShaperConfig; + } + + /** + * Set {@link Uri} to be used for ringtone playback. Attempts to open + * locally, otherwise will delegate playback to remote + * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required. + * + * @hide + */ + public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) { + mVolumeShaperConfig = volumeShaperConfig; + mUri = uri; + if (mUri == null) { + destroyLocalPlayer(); + } + } + + /** {@hide} */ + @UnsupportedAppUsage + public Uri getUri() { + return mUri; + } + + /** + * Plays the ringtone. + */ + public void play() { + if (mLocalPlayer != null) { + // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone + // (typically because ringer mode is vibrate). + if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) + != 0) { + startLocalPlayer(); + } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) { + // is haptic only ringtone + startLocalPlayer(); + } + } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) { + final Uri canonicalUri = mUri.getCanonicalUri(); + final boolean looping; + final float volume; + synchronized (mPlaybackSettingsLock) { + looping = mIsLooping; + volume = mVolume; + } + try { + mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes, + volume, looping, mVolumeShaperConfig); + } catch (RemoteException e) { + if (!playFallbackRingtone()) { + Log.w(TAG, "Problem playing ringtone: " + e); + } + } + } else { + if (!playFallbackRingtone()) { + Log.w(TAG, "Neither local nor remote playback available"); + } + } + } + + /** + * Stops a playing ringtone. + */ + public void stop() { + if (mLocalPlayer != null) { + destroyLocalPlayer(); + } else if (mAllowRemote && (mRemotePlayer != null)) { + try { + mRemotePlayer.stop(mRemoteToken); + } catch (RemoteException e) { + Log.w(TAG, "Problem stopping ringtone: " + e); + } + } + } + + private void destroyLocalPlayer() { + if (mLocalPlayer != null) { + if (mHapticGenerator != null) { + mHapticGenerator.release(); + mHapticGenerator = null; + } + mLocalPlayer.setOnCompletionListener(null); + mLocalPlayer.reset(); + mLocalPlayer.release(); + mLocalPlayer = null; + mVolumeShaper = null; + synchronized (sActiveRingtones) { + sActiveRingtones.remove(this); + } + } + } + + private void startLocalPlayer() { + if (mLocalPlayer == null) { + return; + } + synchronized (sActiveRingtones) { + sActiveRingtones.add(this); + } + if (LOGD) { + Log.d(TAG, "Starting ringtone playback"); + } + mLocalPlayer.setOnCompletionListener(mCompletionListener); + mLocalPlayer.start(); + if (mVolumeShaper != null) { + mVolumeShaper.apply(VolumeShaper.Operation.PLAY); + } + } + + /** + * Whether this ringtone is currently playing. + * + * @return True if playing, false otherwise. + */ + public boolean isPlaying() { + if (mLocalPlayer != null) { + return mLocalPlayer.isPlaying(); + } else if (mAllowRemote && (mRemotePlayer != null)) { + try { + return mRemotePlayer.isPlaying(mRemoteToken); + } catch (RemoteException e) { + Log.w(TAG, "Problem checking ringtone: " + e); + return false; + } + } else { + Log.w(TAG, "Neither local nor remote playback available"); + return false; + } + } + + private boolean playFallbackRingtone() { + int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes); + if (mAudioManager.getStreamVolume(streamType) == 0) { + return false; + } + int ringtoneType = RingtoneManager.getDefaultType(mUri); + if (ringtoneType != -1 && + RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) { + Log.w(TAG, "not playing fallback for " + mUri); + return false; + } + // Default ringtone, try fallback ringtone. + try { + AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( + com.android.internal.R.raw.fallbackring); + if (afd == null) { + Log.e(TAG, "Could not load fallback ringtone"); + return false; + } + mLocalPlayer = new MediaPlayer(); + if (afd.getDeclaredLength() < 0) { + mLocalPlayer.setDataSource(afd.getFileDescriptor()); + } else { + mLocalPlayer.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getDeclaredLength()); + } + mLocalPlayer.setAudioAttributes(mAudioAttributes); + synchronized (mPlaybackSettingsLock) { + applyPlaybackProperties_sync(); + } + if (mVolumeShaperConfig != null) { + mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig); + } + mLocalPlayer.prepare(); + startLocalPlayer(); + afd.close(); + } catch (IOException ioe) { + destroyLocalPlayer(); + Log.e(TAG, "Failed to open fallback ringtone"); + return false; + } catch (NotFoundException nfe) { + Log.e(TAG, "Fallback ringtone does not exist"); + return false; + } + return true; + } + + public boolean getPreferBuiltinDevice() { + return mPreferBuiltinDevice; + } + + public VolumeShaper.Configuration getVolumeShaperConfig() { + return mVolumeShaperConfig; + } + + public boolean isLocalOnly() { + return mAllowRemote; + } + + public boolean isUsingRemotePlayer() { + // V2 testing api, but this is the v1 approximation. + return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null); + } + + class MyOnCompletionListener implements MediaPlayer.OnCompletionListener { + @Override + public void onCompletion(MediaPlayer mp) { + synchronized (sActiveRingtones) { + sActiveRingtones.remove(RingtoneV1.this); + } + mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle. + } + } +} diff --git a/media/java/android/media/RingtoneV2.java b/media/java/android/media/RingtoneV2.java new file mode 100644 index 000000000000..f1a81553bdfc --- /dev/null +++ b/media/java/android/media/RingtoneV2.java @@ -0,0 +1,690 @@ +/* + * 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 android.media; + +import android.annotation.IntDef; +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.Ringtone.Injectables; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.Trace; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.MediaStore; +import android.provider.MediaStore.MediaColumns; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * New Ringtone implementation, supporting vibration as well as sound, and configuration via a + * builder. During flagged transition, the original implementation is in RingtoneV1.java. + * + * Only modified methods are moved here. + * + * @hide + */ +class RingtoneV2 implements Ringtone.ApiInterface { + private static final String TAG = "RingtoneV2"; + + /** + * The ringtone should only play sound. Any vibration is managed externally. + * @hide + */ + public static final int MEDIA_SOUND = 1; + /** + * The ringtone should only play vibration. Any sound is managed externally. + * Requires the {@link android.Manifest.permission#VIBRATE} permission. + * @hide + */ + public static final int MEDIA_VIBRATION = 1 << 1; + /** + * The ringtone should play sound and vibration. + * @hide + */ + public static final int MEDIA_SOUND_AND_VIBRATION = MEDIA_SOUND | MEDIA_VIBRATION; + + // This is not a public value, because apps shouldn't enable "all" media - that wouldn't be + // safe if new media types were added. + static final int MEDIA_ALL = MEDIA_SOUND | MEDIA_VIBRATION; + + /** + * Declares the types of media that this Ringtone is allowed to play. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MEDIA_", value = { + MEDIA_SOUND, + MEDIA_VIBRATION, + MEDIA_SOUND_AND_VIBRATION, + }) + public @interface RingtoneMedia {} + + private static final String[] MEDIA_COLUMNS = new String[] { + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.TITLE + }; + /** Selection that limits query results to just audio files */ + private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR " + + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')"; + + private final Context mContext; + private final Vibrator mVibrator; + private final AudioManager mAudioManager; + private VolumeShaper.Configuration mVolumeShaperConfig; + + /** + * Flag indicating if we're allowed to fall back to remote playback using + * {@link #mRemoteRingtoneService}. Typically this is false when we're the remote + * player and there is nobody else to delegate to. + */ + private final boolean mAllowRemote; + private final IRingtonePlayer mRemoteRingtoneService; + private final Injectables mInjectables; + + private final int mEnabledMedia; + + private final Uri mUri; + private String mTitle; + + private AudioAttributes mAudioAttributes; + private boolean mUseExactAudioAttributes; + private boolean mPreferBuiltinDevice; + private RingtonePlayer mActivePlayer; + // playback properties, use synchronized with mPlaybackSettingsLock + private boolean mIsLooping; + private float mVolume; + private boolean mHapticGeneratorEnabled; + private final Object mPlaybackSettingsLock = new Object(); + private final VibrationEffect mVibrationEffect; + + /** Only for use by Ringtone constructor */ + RingtoneV2(@NonNull Context context, @NonNull Injectables injectables, + boolean allowRemote, @Ringtone.RingtoneMedia int enabledMedia, + @Nullable Uri uri, @NonNull AudioAttributes audioAttributes, + boolean useExactAudioAttributes, + @Nullable VolumeShaper.Configuration volumeShaperConfig, + boolean preferBuiltinDevice, float soundVolume, boolean looping, + boolean hapticGeneratorEnabled, @Nullable VibrationEffect vibrationEffect) { + // Context + mContext = context; + mInjectables = injectables; + mVibrator = mContext.getSystemService(Vibrator.class); + mAudioManager = mContext.getSystemService(AudioManager.class); + mRemoteRingtoneService = allowRemote ? mAudioManager.getRingtonePlayer() : null; + mAllowRemote = (mRemoteRingtoneService != null); // Only set if allowed, and present. + + // Properties potentially propagated to remote player. + mEnabledMedia = enabledMedia; + mUri = uri; + mAudioAttributes = audioAttributes; + mUseExactAudioAttributes = useExactAudioAttributes; + mVolumeShaperConfig = volumeShaperConfig; + mPreferBuiltinDevice = preferBuiltinDevice; // system-only, not supported for remote play. + mVolume = soundVolume; + mIsLooping = looping; + mHapticGeneratorEnabled = hapticGeneratorEnabled; + mVibrationEffect = vibrationEffect; + } + + /** @hide */ + @RingtoneMedia + public int getEnabledMedia() { + return mEnabledMedia; + } + + /** + * Sets the stream type where this ringtone will be played. + * + * @param streamType The stream, see {@link AudioManager}. + * @deprecated use {@link #setAudioAttributes(AudioAttributes)} + */ + @Deprecated + public void setStreamType(int streamType) { + setAudioAttributes( + getAudioAttributesForLegacyStreamType(streamType, "setStreamType()")); + } + + private AudioAttributes getAudioAttributesForLegacyStreamType(int streamType, String originOp) { + PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", originOp); + return new AudioAttributes.Builder() + .setInternalLegacyStreamType(streamType) + .build(); + } + + /** + * Gets the stream type where this ringtone will be played. + * + * @return The stream type, see {@link AudioManager}. + * @deprecated use of stream types is deprecated, see + * {@link #setAudioAttributes(AudioAttributes)} + */ + @Deprecated + public int getStreamType() { + return AudioAttributes.toLegacyStreamType(mAudioAttributes); + } + + /** + * Sets the {@link AudioAttributes} for this ringtone. + * @param attributes the non-null attributes characterizing this ringtone. + */ + public void setAudioAttributes(AudioAttributes attributes) + throws IllegalArgumentException { + // TODO: deprecate this method - it will be done with a builder. + if (attributes == null) { + throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone"); + } + mAudioAttributes = attributes; + // Setting the audio attributes requires re-initializing the player. + if (mActivePlayer != null) { + // The audio attributes have to be set before the media player is prepared. + // Re-initialize it. + reinitializeActivePlayer(); + } + } + + /** + * Returns the vibration effect that this ringtone was created with, if vibration is enabled. + * Otherwise, returns null. + * @hide + */ + @Nullable + public VibrationEffect getVibrationEffect() { + return mVibrationEffect; + } + + /** @hide */ + @VisibleForTesting + public boolean getPreferBuiltinDevice() { + return mPreferBuiltinDevice; + } + + /** @hide */ + @VisibleForTesting + public VolumeShaper.Configuration getVolumeShaperConfig() { + return mVolumeShaperConfig; + } + + /** + * Returns whether this player is local only, or can defer to the remote player. The + * result may differ from the builder if there is no remote player available at all. + * @hide + */ + @VisibleForTesting + public boolean isLocalOnly() { + return !mAllowRemote; + } + + /** @hide */ + @VisibleForTesting + public boolean isUsingRemotePlayer() { + return mActivePlayer instanceof RemoteRingtonePlayer; + } + + /** + * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is + * the one on which outgoing audio for SIM calls is played. + * + * @param audioManager the audio manage. + * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if + * none can be found. + */ + private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) { + AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : deviceList) { + if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + return device; + } + } + return null; + } + + /** + * Creates a local media player for the ringtone using currently set attributes. + * @return true if media player creation succeeded or is deferred, + * false if it did not succeed and can't be tried remotely. + * @hide + */ + public boolean reinitializeActivePlayer() { + // Try creating a local media player, or fallback to creating a remote one. + Trace.beginSection("reinitializeActivePlayer"); + try { + if (mActivePlayer != null) { + // This would only happen if calling the deprecated setAudioAttributes after + // building the Ringtone. + stopAndReleaseActivePlayer(); + } + + boolean vibrationOnly = (mEnabledMedia & MEDIA_ALL) == MEDIA_VIBRATION; + // Vibration can come from the audio file if using haptic generator or if haptic + // channels are a possibility. + boolean maybeAudioVibration = mUri != null && mInjectables.isHapticPlaybackSupported() + && (mHapticGeneratorEnabled || !mAudioAttributes.areHapticChannelsMuted()); + + // VibrationEffect only, use the simplified player without checking for haptic channels. + if (vibrationOnly && !maybeAudioVibration && mVibrationEffect != null) { + mActivePlayer = new LocalRingtonePlayer.VibrationEffectPlayer( + mVibrationEffect, mAudioAttributes, mVibrator, mIsLooping); + return true; + } + + AudioDeviceInfo preferredDevice = + mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null; + if (mUri != null) { + mActivePlayer = LocalRingtonePlayer.create(mContext, mAudioManager, mVibrator, mUri, + mAudioAttributes, vibrationOnly, mVibrationEffect, mInjectables, + mVolumeShaperConfig, preferredDevice, mHapticGeneratorEnabled, mIsLooping, + mVolume); + } else { + // Using the remote player won't help play a null Uri. Revert straight to fallback. + // The vibration-only case was already covered above. + mActivePlayer = createFallbackRingtonePlayer(); + // Fall through to attempting remote fallback play if null. + } + + if (mActivePlayer == null && mAllowRemote) { + mActivePlayer = new RemoteRingtonePlayer(mRemoteRingtoneService, mUri, + mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect, + mVolumeShaperConfig, mHapticGeneratorEnabled, mIsLooping, mVolume); + } + + return mActivePlayer != null; + } finally { + if (mActivePlayer != null) { + Log.d(TAG, "Initialized ringtone player with " + mActivePlayer.getClass()); + } else { + Log.d(TAG, "Failed to initialize ringtone player"); + } + Trace.endSection(); + } + } + + @Nullable + private LocalRingtonePlayer createFallbackRingtonePlayer() { + int ringtoneType = RingtoneManager.getDefaultType(mUri); + if (ringtoneType != -1 + && RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) { + Log.w(TAG, "not playing fallback for " + mUri); + return null; + } + // Default ringtone, try fallback ringtone. + try (AssetFileDescriptor afd = mContext.getResources().openRawResourceFd( + com.android.internal.R.raw.fallbackring)) { + if (afd == null) { + Log.e(TAG, "Could not load fallback ringtone"); + return null; + } + + AudioDeviceInfo preferredDevice = + mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null; + return LocalRingtonePlayer.createForFallback(mAudioManager, mVibrator, afd, + mAudioAttributes, mVibrationEffect, mInjectables, mVolumeShaperConfig, + preferredDevice, mIsLooping, mVolume); + } catch (NotFoundException nfe) { + Log.e(TAG, "Fallback ringtone does not exist"); + return null; + } catch (IOException e) { + // As with the above messages, not including much information about the + // failure so as not to expose details of the fallback ringtone resource. + Log.e(TAG, "Exception reading fallback ringtone"); + return null; + } + } + + /** + * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone. + * @hide + */ + public boolean hasHapticChannels() { + return (mActivePlayer == null) ? false : mActivePlayer.hasHapticChannels(); + } + + /** + * Returns the {@link AudioAttributes} used by this object. + * @return the {@link AudioAttributes} that were set with + * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. + */ + public AudioAttributes getAudioAttributes() { + return mAudioAttributes; + } + + /** + * Sets the player to be looping or non-looping. + * @param looping whether to loop or not. + */ + public void setLooping(boolean looping) { + synchronized (mPlaybackSettingsLock) { + mIsLooping = looping; + if (mActivePlayer != null) { + mActivePlayer.setLooping(looping); + } + } + } + + /** + * Returns whether the looping mode was enabled on this player. + * @return true if this player loops when playing. + */ + public boolean isLooping() { + synchronized (mPlaybackSettingsLock) { + return mIsLooping; + } + } + + /** + * Sets the volume on this player. + * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0 + * corresponds to no attenuation being applied. + */ + public void setVolume(float volume) { + // Ignore if sound not enabled. + if ((mEnabledMedia & MEDIA_SOUND) == 0) { + return; + } + if (volume < 0.0f) { + volume = 0.0f; + } else if (volume > 1.0f) { + volume = 1.0f; + } + + synchronized (mPlaybackSettingsLock) { + mVolume = volume; + if (mActivePlayer != null) { + mActivePlayer.setVolume(volume); + } + } + } + + /** + * Returns the volume scalar set on this player. + * @return a value between 0.0f and 1.0f. + */ + public float getVolume() { + synchronized (mPlaybackSettingsLock) { + return mVolume; + } + } + + /** + * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can + * only be enabled on devices that support the effect. + * + * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false. + * @see android.media.audiofx.HapticGenerator#isAvailable() + */ + public boolean setHapticGeneratorEnabled(boolean enabled) { + if (!mInjectables.isHapticGeneratorAvailable()) { + return false; + } + synchronized (mPlaybackSettingsLock) { + mHapticGeneratorEnabled = enabled; + if (mActivePlayer != null) { + mActivePlayer.setHapticGeneratorEnabled(enabled); + } + } + return true; + } + + /** + * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not. + * @return true if the HapticGenerator is enabled. + */ + public boolean isHapticGeneratorEnabled() { + synchronized (mPlaybackSettingsLock) { + return mHapticGeneratorEnabled; + } + } + + /** + * Returns a human-presentable title for ringtone. Looks in media + * content provider. If not in either, uses the filename + * + * @param context A context used for querying. + */ + public String getTitle(Context context) { + if (mTitle != null) return mTitle; + return mTitle = Ringtone.getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote); + } + + + /** {@hide} */ + @UnsupportedAppUsage + public Uri getUri() { + return mUri; + } + + /** + * Plays the ringtone. + */ + public void play() { + if (mActivePlayer != null) { + Log.d(TAG, "Starting ringtone playback"); + if (mActivePlayer.play()) { + return; + } else { + // Discard active player: play() is only meant to be called once. + stopAndReleaseActivePlayer(); + } + } + if (!playFallbackRingtone()) { + Log.w(TAG, "Neither local nor remote playback available"); + } + } + + /** + * Stops a playing ringtone. + */ + public void stop() { + stopAndReleaseActivePlayer(); + } + + private void stopAndReleaseActivePlayer() { + if (mActivePlayer != null) { + mActivePlayer.stopAndRelease(); + mActivePlayer = null; + } + } + + /** + * Whether this ringtone is currently playing. + * + * @return True if playing, false otherwise. + */ + public boolean isPlaying() { + if (mActivePlayer != null) { + return mActivePlayer.isPlaying(); + } else { + Log.w(TAG, "No active ringtone player"); + return false; + } + } + + /** + * Fallback during the play stage rather than initialization, typically due to an issue + * communicating with the remote player. + */ + private boolean playFallbackRingtone() { + if (mActivePlayer != null) { + Log.wtf(TAG, "Playing fallback ringtone with another active player"); + stopAndReleaseActivePlayer(); + } + int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes); + if (mAudioManager.getStreamVolume(streamType) == 0) { + // TODO: Return true? If volume is off, this is a successful play. + return false; + } + mActivePlayer = createFallbackRingtonePlayer(); + if (mActivePlayer == null) { + return false; // the create method logs if it returns null. + } else if (mActivePlayer.play()) { + return true; + } else { + stopAndReleaseActivePlayer(); + return false; + } + } + + void setTitle(String title) { + mTitle = title; + } + + /** + * Play a specific ringtone. This interface is implemented by either local (this process) or + * proxied-remote playback via AudioManager.getRingtonePlayer, so that the caller + * (Ringtone class) can just use a single player after the initial creation. + * @hide + */ + interface RingtonePlayer { + /** + * Start playing the ringtone, returning false if there was a problem that + * requires falling back to the fallback ringtone resource. + */ + boolean play(); + boolean isPlaying(); + void stopAndRelease(); + + // Mutating playback methods. + void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo); + void setLooping(boolean looping); + void setHapticGeneratorEnabled(boolean enabled); + void setVolume(float volume); + + boolean hasHapticChannels(); + } + + /** + * Remote RingtonePlayer. All operations are delegated via the IRingtonePlayer interface, which + * should ultimately be backed by a RingtoneLocalPlayer within the system services. + */ + static class RemoteRingtonePlayer implements RingtonePlayer { + private final IBinder mRemoteToken = new Binder(); + private final IRingtonePlayer mRemoteRingtoneService; + private final Uri mCanonicalUri; + private final int mEnabledMedia; + private final VibrationEffect mVibrationEffect; + private final VolumeShaper.Configuration mVolumeShaperConfig; + private final AudioAttributes mAudioAttributes; + private final boolean mUseExactAudioAttributes; + private boolean mIsLooping; + private float mVolume; + private boolean mHapticGeneratorEnabled; + + RemoteRingtonePlayer(@NonNull IRingtonePlayer remoteRingtoneService, + @NonNull Uri uri, @NonNull AudioAttributes audioAttributes, + boolean useExactAudioAttributes, + @RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect, + @Nullable VolumeShaper.Configuration volumeShaperConfig, + boolean hapticGeneratorEnabled, boolean initialIsLooping, float initialVolume) { + mRemoteRingtoneService = remoteRingtoneService; + mCanonicalUri = (uri == null) ? null : uri.getCanonicalUri(); + mAudioAttributes = audioAttributes; + mUseExactAudioAttributes = useExactAudioAttributes; + mEnabledMedia = enabledMedia; + mVibrationEffect = vibrationEffect; + mVolumeShaperConfig = volumeShaperConfig; + mHapticGeneratorEnabled = hapticGeneratorEnabled; + mIsLooping = initialIsLooping; + mVolume = initialVolume; + } + + @Override + public boolean play() { + try { + mRemoteRingtoneService.playRemoteRingtone(mRemoteToken, mCanonicalUri, + mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect, + mVolume, mIsLooping, mHapticGeneratorEnabled, mVolumeShaperConfig); + return true; + } catch (RemoteException e) { + Log.w(TAG, "Problem playing ringtone: " + e); + return false; + } + } + + @Override + public boolean isPlaying() { + try { + return mRemoteRingtoneService.isPlaying(mRemoteToken); + } catch (RemoteException e) { + Log.w(TAG, "Problem checking ringtone isPlaying: " + e); + return false; + } + } + + @Override + public void stopAndRelease() { + try { + mRemoteRingtoneService.stop(mRemoteToken); + } catch (RemoteException e) { + Log.w(TAG, "Problem stopping ringtone: " + e); + } + } + + @Override + public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { + // un-implemented for remote (but not used outside system). + } + + @Override + public void setLooping(boolean looping) { + mIsLooping = looping; + try { + mRemoteRingtoneService.setLooping(mRemoteToken, looping); + } catch (RemoteException e) { + Log.w(TAG, "Problem setting looping: " + e); + } + } + + @Override + public void setHapticGeneratorEnabled(boolean enabled) { + mHapticGeneratorEnabled = enabled; + try { + mRemoteRingtoneService.setHapticGeneratorEnabled(mRemoteToken, enabled); + } catch (RemoteException e) { + Log.w(TAG, "Problem setting hapticGeneratorEnabled: " + e); + } + } + + @Override + public void setVolume(float volume) { + mVolume = volume; + try { + mRemoteRingtoneService.setVolume(mRemoteToken, volume); + } catch (RemoteException e) { + Log.w(TAG, "Problem setting volume: " + e); + } + } + + @Override + public boolean hasHapticChannels() { + // FIXME: support remote player, or internalize haptic channels support and remove + // entirely. + return false; + } + } + +} diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index 0c6096ecad4d..6e2aaabf4b04 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -130,7 +130,7 @@ public abstract class MidiUmpDeviceService extends Service { /** * Returns the {@link MidiDeviceInfo} instance for this service - * @return the MidiDeviceInfo of the virtual MIDI device + * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created */ public final @Nullable MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; @@ -140,7 +140,7 @@ public abstract class MidiUmpDeviceService extends Service { * Called to notify when the {@link MidiDeviceStatus} has changed * @param status the current status of the MIDI device */ - public void onDeviceStatusChanged(@Nullable MidiDeviceStatus status) { + public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) { } /** diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 864a8bb17058..c8df7602800f 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -123,8 +123,7 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec @Override public boolean performClick() { - mSwitch.performClick(); - return super.performClick(); + return mSwitch.performClick(); } /** diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp index 79f8c463b5ae..7f5948c36e8d 100644 --- a/packages/SettingsLib/Spa/spa/Android.bp +++ b/packages/SettingsLib/Spa/spa/Android.bp @@ -22,7 +22,7 @@ android_library { name: "SpaLib", srcs: ["src/**/*.kt"], - + use_resource_processor: true, static_libs: [ "androidx.slice_slice-builders", "androidx.slice_slice-core", @@ -50,5 +50,6 @@ android_library { // Expose the srcs to tests, so the tests can access the internal classes. filegroup { name: "SpaLib_srcs", + visibility: ["//frameworks/base/packages/SettingsLib/Spa/tests"], srcs: ["src/**/*.kt"], } diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp index e4d56cc4f2a0..65f5d34bd8d1 100644 --- a/packages/SettingsLib/Spa/testutils/Android.bp +++ b/packages/SettingsLib/Spa/testutils/Android.bp @@ -22,7 +22,7 @@ android_library { name: "SpaLibTestUtils", srcs: ["src/**/*.kt"], - + use_resource_processor: true, static_libs: [ "SpaLib", "androidx.arch.core_core-testing", diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp index 4a7418fd101d..eaeda3c67545 100644 --- a/packages/SettingsLib/SpaPrivileged/Android.bp +++ b/packages/SettingsLib/SpaPrivileged/Android.bp @@ -22,7 +22,7 @@ android_library { name: "SpaPrivilegedLib", srcs: ["src/**/*.kt"], - + use_resource_processor: true, static_libs: [ "SpaLib", "SettingsLib", @@ -45,5 +45,6 @@ java_defaults { // Expose the srcs to tests, so the tests can access the internal classes. filegroup { name: "SpaPrivilegedLib_srcs", + visibility: [":__subpackages__"], srcs: ["src/**/*.kt"], } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt new file mode 100644 index 000000000000..05cb1b149704 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.framework.compose + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.android.settingslib.R + +/** An empty placer holder string. */ +@Composable +fun placeholder() = stringResource(R.string.summary_placeholder) + +/** Gets an empty placer holder string. */ +fun Context.getPlaceholder(): String = getString(R.string.summary_placeholder) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt index 1a7d8968f232..de2cf1f5fdf6 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt @@ -23,11 +23,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import com.android.settingslib.Utils import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.framework.common.userManager +import com.android.settingslib.spaprivileged.framework.compose.placeholder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -40,7 +40,7 @@ interface AppRepository { @Composable fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> { val context = LocalContext.current - return produceState(initialValue = stringResource(R.string.summary_placeholder), app) { + return produceState(initialValue = placeholder(), app) { withContext(Dispatchers.IO) { value = if (isClonedAppPage || isCloneApp(context, app)) { context.getString(R.string.cloned_app_info_label, loadLabel(app)) @@ -82,7 +82,7 @@ internal class AppRepositoryImpl(private val context: Context) : AppRepository { withContext(Dispatchers.IO) { value = when { context.userManager.isManagedProfile(app.userId) -> { - context.getString(R.string.category_work) + context.getString(com.android.settingslib.R.string.category_work) } else -> null diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt index fab3ae8e510b..cc3584b6fb11 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt @@ -20,7 +20,7 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER import android.content.Context -import com.android.settingslib.spaprivileged.R +import com.android.settingslib.R class EnterpriseRepository(private val context: Context) { private val resources by lazy { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index ae362c894e6d..e2ff7b0bc2f8 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -27,7 +27,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settingslib.RestrictedLockUtils import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin import com.android.settingslib.RestrictedLockUtilsInternal -import com.android.settingslib.spaprivileged.R +import com.android.settingslib.widget.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @@ -56,11 +56,15 @@ private data class BlockedByAdminImpl( override fun getSummary(checked: Boolean?) = when (checked) { true -> enterpriseRepository.getEnterpriseString( - Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin + updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, + resId = R.string.enabled_by_admin, ) + false -> enterpriseRepository.getEnterpriseString( - Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.disabled_by_admin + updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, + resId = R.string.disabled_by_admin, ) + else -> "" } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index b43210f0c3e4..cee750e186e0 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -72,7 +72,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { private fun InstallType(app: ApplicationInfo) { if (!app.isInstantApp) return Spacer(modifier = Modifier.height(4.dp)) - SettingsBody(stringResource(R.string.install_type_instant)) + SettingsBody(stringResource(com.android.settingslib.widget.R.string.install_type_instant)) } @Composable diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt index 3e96994c2de6..5fc1972fc878 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt @@ -24,9 +24,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.framework.common.storageStatsManager +import com.android.settingslib.spaprivileged.framework.compose.placeholder import com.android.settingslib.spaprivileged.model.app.userHandle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -36,7 +35,7 @@ private const val TAG = "AppStorageSize" @Composable fun ApplicationInfo.getStorageSize(): State<String> { val context = LocalContext.current - return produceState(initialValue = stringResource(R.string.summary_placeholder)) { + return produceState(initialValue = placeholder()) { withContext(Dispatchers.IO) { val sizeBytes = calculateSizeBytes(context) value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else "" diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index cbc4822f2896..1fa854a4c09e 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -38,6 +38,7 @@ import com.android.settingslib.spa.framework.util.getStringArg import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.userId @@ -173,7 +174,7 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>( when (allowed.value) { true -> context.getString(R.string.app_permission_summary_allowed) false -> context.getString(R.string.app_permission_summary_not_allowed) - null -> context.getString(R.string.summary_placeholder) + null -> context.getPlaceholder() } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt index b08b6dfa67a6..e77dcd4d9cc4 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.state.ToggleableState import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted @@ -72,9 +72,12 @@ internal object RestrictedSwitchPreference { checked: State<Boolean?>, ): State<String> = when (restrictedMode) { is NoRestricted -> summaryIfNoRestricted - is BaseUserRestricted -> stateOf(context.getString(R.string.disabled)) + is BaseUserRestricted -> stateOf( + context.getString(com.android.settingslib.R.string.disabled) + ) + is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) } - null -> stateOf(context.getString(R.string.summary_placeholder)) + null -> stateOf(context.getPlaceholder()) } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt index 26caa01192c5..d11e63ae048f 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt @@ -25,7 +25,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.testutils.delay -import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -66,7 +65,8 @@ class AppRepositoryTest { val contentDescription = produceIconContentDescription() - assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work)) + assertThat(contentDescription.value) + .isEqualTo(context.getString(com.android.settingslib.R.string.category_work)) } @Test diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt index f6f48891030a..82fbee9a8c43 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt @@ -116,7 +116,7 @@ class AppListPageTest { private fun onMoreOptions() = composeTestRule.onNodeWithContentDescription( - context.getString(R.string.abc_action_menu_overflow_description) + context.getString(androidx.appcompat.R.string.abc_action_menu_overflow_description) ) private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt index 961ec10cccd9..457b810f2dec 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt @@ -30,6 +30,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.testutils.FakeNavControllerWrapper import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord @@ -95,9 +96,7 @@ class TogglePermissionAppListPageTest { val summaryState = getSummary(listModel) - assertThat(summaryState.value).isEqualTo( - context.getString(R.string.summary_placeholder) - ) + assertThat(summaryState.value).isEqualTo(context.getPlaceholder()) } @Test diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java index 3ec5ebad0de6..363e20aace0d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -133,6 +133,12 @@ public class PrimarySwitchPreference extends RestrictedPreference { } } + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + setSwitchEnabled(enabled); + } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public boolean isSwitchEnabled() { return mEnableSwitch; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index cb8e7e8bb6ef..dcb0a0712477 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -23,6 +23,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.UserHandle; import android.text.TextUtils; @@ -229,13 +230,15 @@ public class RestrictedPreferenceHelper { } private void updateDisabledState() { + boolean isEnabled = !(mDisabledByAdmin || mDisabledByAppOps); if (!(mPreference instanceof RestrictedTopLevelPreference)) { - mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + mPreference.setEnabled(isEnabled); } - if (mPreference instanceof PrimarySwitchPreference) { - ((PrimarySwitchPreference) mPreference) - .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + Drawable icon = mPreference.getIcon(); + if (!isEnabled && icon != null) { + Utils.convertToGrayscale(icon); + mPreference.setIcon(icon); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index c967b568042c..9d533fabda6d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -700,4 +700,14 @@ public class Utils { return false; } + /** + * Convert a drawable to grayscale drawable + */ + public static void convertToGrayscale(@NonNull Drawable drawable) { + ColorMatrix matrix = new ColorMatrix(); + matrix.setSaturation(0.0f); + + ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix); + drawable.setColorFilter(filter); + } } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 41ce58eb7b4e..d1f7f2fe3516 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -105,5 +105,6 @@ public class GlobalSettings { Settings.Global.HDR_FORCE_CONVERSION_TYPE, Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, Settings.Global.Wearable.REDUCE_MOTION, + Settings.Global.Wearable.WEAR_LAUNCHER_UI_MODE, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index e31a672eaa59..46e73d096c0b 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -441,5 +441,6 @@ public class GlobalSettingsValidators { String.valueOf(Global.Wearable.TETHERED_CONFIG_TETHERED) })); VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, NON_NEGATIVE_INTEGER_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index d1d745fe2b93..7e9795643849 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1997,7 +1997,7 @@ public class SettingsProvider extends ContentProvider { case MUTATION_OPERATION_RESET: { mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SYSTEM, - UserHandle.USER_SYSTEM, callingPackage, mode, tag); + runAsUserId, callingPackage, mode, tag); return true; } } @@ -3254,6 +3254,15 @@ public class SettingsProvider extends ContentProvider { return settingsState.getSettingLocked(name); } + private static boolean shouldExcludeSettingFromReset(Setting setting, String prefix) { + // If a prefix was specified, exclude settings whose names don't start with it. + if (prefix != null && !setting.getName().startsWith(prefix)) { + return true; + } + // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty. + return Global.SECURE_FRP_MODE.equals(setting.getName()); + } + public void resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ @@ -3276,7 +3285,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (packageName.equals(setting.getPackageName())) { if ((tag != null && !tag.equals(setting.getTag())) - || (prefix != null && !setting.getName().startsWith(prefix))) { + || shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3297,7 +3306,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (settingsState.resetSettingLocked(name)) { @@ -3318,7 +3327,7 @@ public class SettingsProvider extends ContentProvider { Setting setting = settingsState.getSettingLocked(name); if (!SettingsState.isSystemPackage(getContext(), setting.getPackageName())) { - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { @@ -3343,7 +3352,7 @@ public class SettingsProvider extends ContentProvider { for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; - if (prefix != null && !setting.getName().startsWith(prefix)) { + if (shouldExcludeSettingFromReset(setting, prefix)) { continue; } if (setting.isDefaultFromSystem()) { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 016dc2144d3f..9c9dd8a6c39e 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -722,6 +722,7 @@ public class SettingsBackupTest { Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED, Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN, Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY, + Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user Settings.Secure.BACKUP_AUTO_RESTORE, Settings.Secure.BACKUP_ENABLED, Settings.Secure.BACKUP_PROVISIONED, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java index eaf0dcb9b4e7..a945c33bc20a 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java @@ -464,6 +464,31 @@ public class SettingsProviderTest extends BaseSettingsProviderTest { } } + // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other + // settings are reset. But it should still be possible to explicitly set its value. + @Test + public void testSecureFrpModeSettingCannotBeReset() throws Exception { + final String name = Settings.Global.SECURE_FRP_MODE; + final String origValue = getSetting(SETTING_TYPE_GLOBAL, name); + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false); + try { + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) { + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES); + resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS); + } + // The value should still be "1". It should not have been reset to null. + assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name)); + // It should still be possible to explicitly set the value to "0". + setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false); + assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name)); + } finally { + setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false); + assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name)); + } + } + private void doTestQueryStringInBracketsViaProviderApiForType(int type) { // Make sure we have a clean slate. deleteStringViaProviderApi(type, FAKE_SETTING_NAME); diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java index cb41eabfa87a..6817f534c00b 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java @@ -19,6 +19,7 @@ package com.android.soundpicker; import android.content.Context; import android.media.AudioAttributes; import android.media.Ringtone; +import android.media.RingtoneManager; import android.net.Uri; import dagger.hilt.android.qualifiers.ApplicationContext; @@ -53,10 +54,7 @@ public class RingtoneFactory { .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setFlags(audioAttributesFlags) .build(); - // TODO: We are currently only using MEDIA_SOUND for enabledMedia. This will change once we - // start playing sound and/or vibration. - return new Ringtone.Builder(mApplicationContext, Ringtone.MEDIA_SOUND, audioAttributes) - .setUri(uri) - .build(); + return RingtoneManager.getRingtone(mApplicationContext, uri, + /* volumeShaperConfig= */ null, audioAttributes); } } diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 1bd83551886a..969c14821350 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -56,6 +56,20 @@ ] }, { + "name": "SystemUIGoogleScreenshotTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.Postsubmit" + } + ] + }, + { // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+) "name": "SystemUIGoogleBiometricsScreenshotTests", "options": [ @@ -148,5 +162,21 @@ } ] } + ], + "postsubmit": [ + { + "name": "SystemUIGoogleScreenshotTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "include-annotation": "android.platform.test.annotations.Postsubmit" + } + ] + } ] } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 6d9497dac8ea..b9baa7930b4e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope import com.android.systemui.R import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel @@ -81,7 +82,7 @@ constructor( .asStateFlow() @Composable - override fun Content( + override fun SceneScope.Content( modifier: Modifier, ) = BouncerScene(viewModel, dialogFactory, modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index ab7bc26d59e1..ca7352ef2501 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton @@ -66,7 +67,7 @@ constructor( ) @Composable - override fun Content( + override fun SceneScope.Content( modifier: Modifier, ) { LockscreenScene( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 130395a38512..29763c2e329d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.Direction @@ -57,7 +58,7 @@ constructor( .asStateFlow() @Composable - override fun Content( + override fun SceneScope.Content( modifier: Modifier, ) { QuickSettingsScene( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt index a21366695f66..3da6a02d08d3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt @@ -18,9 +18,10 @@ package com.android.systemui.scene.ui.composable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope import com.android.systemui.scene.shared.model.Scene /** Compose-capable extension of [Scene]. */ interface ComposableScene : Scene { - @Composable fun Content(modifier: Modifier) + @Composable fun SceneScope.Content(modifier: Modifier) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index 007055221691..774c409f34df 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -50,7 +51,7 @@ class GoneScene @Inject constructor() : ComposableScene { .asStateFlow() @Composable - override fun Content( + override fun SceneScope.Content( modifier: Modifier, ) { /* diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 49e2bf97b3f0..3dfdbbaaee77 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -14,32 +14,31 @@ * limitations under the License. */ -@file:OptIn(ExperimentalAnimationApi::class) - package com.android.systemui.scene.ui.composable -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.Back +import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState +import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.SceneTransitionLayoutState +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction +import com.android.compose.animation.scene.observableTransitionState +import com.android.compose.animation.scene.transitions import com.android.systemui.scene.shared.model.Direction +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import java.util.Locale +import kotlinx.coroutines.flow.map /** * Renders a container of a collection of "scenes" that the user can switch between using certain @@ -64,77 +63,94 @@ fun SceneContainer( sceneByKey: Map<SceneKey, ComposableScene>, modifier: Modifier = Modifier, ) { - val currentScene: SceneModel by viewModel.currentScene.collectAsState() + val currentSceneModel: SceneModel by viewModel.currentScene.collectAsState() + val currentSceneKey = currentSceneModel.key + val currentScene = checkNotNull(sceneByKey[currentSceneKey]) + val currentDestinations: Map<UserAction, SceneModel> by + currentScene.destinationScenes().collectAsState() + val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) } + + DisposableEffect(viewModel, state) { + viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() }) + onDispose { viewModel.setTransitionState(null) } + } - AnimatedContent( - targetState = currentScene.key, - label = "scene container", - modifier = modifier, - ) { currentSceneKey -> - sceneByKey.forEach { (key, composableScene) -> - if (key == currentSceneKey) { - Scene( - scene = composableScene, - onSceneChanged = viewModel::setCurrentScene, - modifier = Modifier.fillMaxSize(), - ) + SceneTransitionLayout( + currentScene = currentSceneKey.toTransitionSceneKey(), + onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) }, + transitions = transitions {}, + state = state, + modifier = modifier.fillMaxSize(), + ) { + sceneByKey.forEach { (sceneKey, composableScene) -> + scene( + key = sceneKey.toTransitionSceneKey(), + userActions = + if (sceneKey == currentSceneKey) { + currentDestinations + } else { + composableScene.destinationScenes().value + } + .map { (userAction, destinationSceneModel) -> + toTransitionModels(userAction, destinationSceneModel) + } + .toMap(), + ) { + with(composableScene) { + this@scene.Content( + modifier = Modifier.fillMaxSize(), + ) + } } } } } -/** Renders the given [ComposableScene]. */ -@Composable -private fun Scene( - scene: ComposableScene, - onSceneChanged: (SceneModel) -> Unit, - modifier: Modifier = Modifier, -) { - val destinationScenes: Map<UserAction, SceneModel> by scene.destinationScenes().collectAsState() - val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)] - val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)] - val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)] - val swipeDownDestinationScene = destinationScenes[UserAction.Swipe(Direction.DOWN)] - val backDestinationScene = destinationScenes[UserAction.Back] - - // TODO(b/280880714): replace with the real UI and make sure to call onTransitionProgress. - Box(modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.Center), - ) { - scene.Content( - modifier = Modifier, +// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout. +private fun SceneTransitionObservableTransitionState.toModel(): ObservableTransitionState { + return when (this) { + is SceneTransitionObservableTransitionState.Idle -> + ObservableTransitionState.Idle(scene.toModel().key) + is SceneTransitionObservableTransitionState.Transition -> + ObservableTransitionState.Transition( + fromScene = fromScene.toModel().key, + toScene = toScene.toModel().key, + progress = progress, ) + } +} - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - DirectionalButton(Direction.LEFT, swipeLeftDestinationScene, onSceneChanged) - DirectionalButton(Direction.UP, swipeUpDestinationScene, onSceneChanged) - DirectionalButton(Direction.RIGHT, swipeRightDestinationScene, onSceneChanged) - DirectionalButton(Direction.DOWN, swipeDownDestinationScene, onSceneChanged) - } +// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout. +private fun toTransitionModels( + userAction: UserAction, + sceneModel: SceneModel, +): Pair<SceneTransitionUserAction, SceneTransitionSceneKey> { + return userAction.toTransitionUserAction() to sceneModel.key.toTransitionSceneKey() +} - if (backDestinationScene != null) { - BackHandler { onSceneChanged.invoke(backDestinationScene) } - } - } - } +// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout. +private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey { + return SceneTransitionSceneKey( + name = toString(), + identity = this, + ) } -@Composable -private fun DirectionalButton( - direction: Direction, - destinationScene: SceneModel?, - onSceneChanged: (SceneModel) -> Unit, - modifier: Modifier = Modifier, -) { - Button( - onClick = { destinationScene?.let { onSceneChanged.invoke(it) } }, - enabled = destinationScene != null, - modifier = modifier, - ) { - Text(direction.name.lowercase(Locale.getDefault())) +// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout. +private fun SceneTransitionSceneKey.toModel(): SceneModel { + return SceneModel(key = identity as SceneKey) +} + +// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout. +private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction { + return when (this) { + is UserAction.Swipe -> + when (this.direction) { + Direction.LEFT -> Swipe.Left + Direction.UP -> Swipe.Up + Direction.RIGHT -> Swipe.Right + Direction.DOWN -> Swipe.Down + } + is UserAction.Back -> Back } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index b73e0b26f208..ff1cb5f1afa3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.Notifications @@ -63,7 +64,7 @@ constructor( ) @Composable - override fun Content( + override fun SceneScope.Content( modifier: Modifier, ) = ShadeScene(viewModel, modifier) @@ -86,11 +87,12 @@ private fun ShadeScene( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp), modifier = - Modifier.fillMaxSize() + modifier + .fillMaxSize() .clickable(onClick = { viewModel.onContentClicked() }) .padding(horizontal = 16.dp, vertical = 48.dp) ) { - QuickSettings(modifier = modifier.height(160.dp)) - Notifications(modifier = modifier.weight(1f)) + QuickSettings(modifier = Modifier.height(160.dp)) + Notifications(modifier = Modifier.weight(1f)) } } diff --git a/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml new file mode 100644 index 000000000000..2fb722248229 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/transparent" /> + <size + android:width="@dimen/control_list_horizontal_spacing" + android:height="@dimen/control_list_vertical_spacing" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/controls_list_divider.xml b/packages/SystemUI/res/drawable/global_actions_list_divider.xml index f8211d5297f3..f8211d5297f3 100644 --- a/packages/SystemUI/res/drawable/controls_list_divider.xml +++ b/packages/SystemUI/res/drawable/global_actions_list_divider.xml diff --git a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml index ddfa18c53db9..170bc0def305 100644 --- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml +++ b/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml @@ -15,6 +15,6 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/controls_list_divider" + android:drawable="@drawable/global_actions_list_divider" android:insetRight="@dimen/control_spinner_padding_horizontal" android:insetLeft="@dimen/control_spinner_padding_horizontal" /> diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml index ecb0bfa35e9f..bea0e13c77dc 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -28,7 +28,6 @@ android:singleLine="true" android:marqueeRepeatLimit="1" android:ellipsize="marquee" - android:importantForAccessibility="no" style="@style/TextAppearance.AuthCredential.Title"/> <TextView @@ -39,7 +38,6 @@ android:singleLine="true" android:marqueeRepeatLimit="1" android:ellipsize="marquee" - android:importantForAccessibility="no" style="@style/TextAppearance.AuthCredential.Subtitle"/> <TextView diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index e1dbe695bd0e..f1939bb278e8 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -25,7 +25,6 @@ android:focusable="true" android:screenReaderFocusable="true" android:stateListAnimator="@anim/control_state_list_animator" - android:layout_marginStart="@dimen/control_spacing" android:background="@drawable/control_background"> <ImageView diff --git a/packages/SystemUI/res/layout/controls_list_view.xml b/packages/SystemUI/res/layout/controls_list_view.xml new file mode 100644 index 000000000000..2831cbfce043 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_list_view.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/controls_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:divider="@drawable/controls_list_divider" + android:orientation="vertical" + android:showDividers="middle" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml index 4cc461a28187..4923b053aa2f 100644 --- a/packages/SystemUI/res/layout/controls_row.xml +++ b/packages/SystemUI/res/layout/controls_row.xml @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="horizontal" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/control_spacing" /> + android:divider="@drawable/controls_list_divider" + android:orientation="horizontal" + android:showDividers="middle" /> diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index b1259e48f7f7..c60fb2660462 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -20,7 +20,7 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/controls_header_menu_size" android:paddingHorizontal="@dimen/controls_header_horizontal_padding" android:layout_marginBottom="@dimen/controls_header_bottom_margin" android:orientation="horizontal"> @@ -85,10 +85,11 @@ android:layout_weight="1" android:clipChildren="true" android:orientation="vertical" - android:paddingHorizontal="16dp" + android:padding="@dimen/controls_content_padding" + android:background="@drawable/controls_panel_background" android:scrollbars="none"> - <include layout="@layout/global_actions_controls_list_view" /> + <include layout="@layout/controls_list_view" /> </ScrollView> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index 8bff1a135336..6de10b405aba 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -14,34 +14,27 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<FrameLayout +<com.android.systemui.shared.shadow.DoubleShadowTextClock xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" - android:layout_height="wrap_content"> - - <com.android.systemui.shared.shadow.DoubleShadowTextClock - android:id="@+id/time_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_clockFontFamily" - android:textColor="@android:color/white" - android:format12Hour="@string/dream_time_complication_12_hr_time_format" - android:format24Hour="@string/dream_time_complication_24_hr_time_format" - android:fontFeatureSettings="pnum, lnum" - android:includeFontPadding="false" - android:letterSpacing="0.02" - android:maxLines="1" - android:textSize="@dimen/dream_overlay_complication_clock_time_text_size" - app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius" - app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx" - app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy" - app:keyShadowAlpha="0.3" - app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius" - app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx" - app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy" - app:ambientShadowAlpha="0.3" - app:removeTextDescent="true" - app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" /> - -</FrameLayout> + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_clockFontFamily" + android:textColor="@android:color/white" + android:format12Hour="@string/dream_time_complication_12_hr_time_format" + android:format24Hour="@string/dream_time_complication_24_hr_time_format" + android:fontFeatureSettings="pnum, lnum" + android:includeFontPadding="false" + android:letterSpacing="0.02" + android:maxLines="1" + android:textSize="@dimen/dream_overlay_complication_clock_time_text_size" + app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius" + app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx" + app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy" + app:keyShadowAlpha="0.3" + app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius" + app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx" + app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy" + app:ambientShadowAlpha="0.3" + app:removeTextDescent="true" + app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" /> diff --git a/packages/SystemUI/res/layout/global_actions_controls_list_view.xml b/packages/SystemUI/res/layout/global_actions_controls_list_view.xml deleted file mode 100644 index e1c2611cc4d4..000000000000 --- a/packages/SystemUI/res/layout/global_actions_controls_list_view.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/global_actions_controls_list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_marginLeft="@dimen/global_actions_side_margin" - android:layout_marginRight="@dimen/global_actions_side_margin" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_actions_power_dialog.xml b/packages/SystemUI/res/layout/global_actions_power_dialog.xml index ff3f0fb74cd5..3456515de635 100644 --- a/packages/SystemUI/res/layout/global_actions_power_dialog.xml +++ b/packages/SystemUI/res/layout/global_actions_power_dialog.xml @@ -18,7 +18,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - android:divider="@drawable/controls_list_divider" + android:divider="@drawable/global_actions_list_divider" android:showDividers="middle" /> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 1681f7a95bce..0667cd8b12d6 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -69,6 +69,9 @@ <dimen name="controls_header_horizontal_padding">12dp</dimen> <dimen name="controls_content_margin_horizontal">16dp</dimen> + <dimen name="controls_content_padding">16dp</dimen> + <dimen name="control_list_vertical_spacing">8dp</dimen> + <dimen name="control_list_horizontal_spacing">16dp</dimen> <!-- Rear Display Education dimens --> <dimen name="rear_display_animation_width">246dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 2b1d9d6c2f10..7e892f7e4800 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -103,4 +103,7 @@ <dimen name="controls_header_horizontal_padding">12dp</dimen> <dimen name="controls_content_margin_horizontal">24dp</dimen> + <dimen name="controls_content_padding">24dp</dimen> + <dimen name="control_list_vertical_spacing">8dp</dimen> + <dimen name="control_list_horizontal_spacing">16dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index de913ace8fda..d74eca6e127d 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -24,6 +24,9 @@ <dimen name="controls_header_horizontal_padding">28dp</dimen> <dimen name="controls_content_margin_horizontal">40dp</dimen> + <dimen name="controls_content_padding">32dp</dimen> + <dimen name="control_list_vertical_spacing">16dp</dimen> + <dimen name="control_list_horizontal_spacing">16dp</dimen> <dimen name="large_screen_shade_header_height">56dp</dimen> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 9f4fc396ae55..e9422580ed54 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -195,7 +195,7 @@ <color name="control_primary_text">#E6FFFFFF</color> <color name="control_secondary_text">#99FFFFFF</color> <color name="control_default_foreground">@color/GM2_grey_500</color> - <color name="control_default_background">@color/GM2_grey_900</color> + <color name="control_default_background">#303134</color> <color name="control_spinner_dropdown">@*android:color/foreground_material_dark</color> <color name="control_more_vert">@*android:color/foreground_material_dark</color> <color name="control_enabled_light_background">#413C2D</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index de8287e5e8cb..34f8f2d89be2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1196,7 +1196,6 @@ <dimen name="magnification_window_drag_corner_stroke">3dp</dimen> <!-- The extra padding to show the whole outer border --> <dimen name="magnifier_drag_handle_padding">3dp</dimen> - <dimen name="magnification_max_frame_size">300dp</dimen> <!-- Magnification settings panel --> <dimen name="magnification_setting_view_margin">24dp</dimen> <dimen name="magnification_setting_text_size">18sp</dimen> @@ -1238,6 +1237,7 @@ <dimen name="controls_header_app_icon_size">24dp</dimen> <dimen name="controls_top_margin">48dp</dimen> <dimen name="controls_content_margin_horizontal">0dp</dimen> + <dimen name="controls_content_padding">16dp</dimen> <dimen name="control_header_text_size">24sp</dimen> <dimen name="control_item_text_size">14sp</dimen> <dimen name="control_menu_item_text_size">16sp</dimen> @@ -1256,6 +1256,8 @@ <dimen name="control_chevron_icon_size">20dp</dimen> <dimen name="control_spacing">8dp</dimen> <dimen name="control_list_divider">1dp</dimen> + <dimen name="control_list_vertical_spacing">8dp</dimen> + <dimen name="control_list_horizontal_spacing">12dp</dimen> <dimen name="control_corner_radius">14dp</dimen> <dimen name="control_height">104dp</dimen> <dimen name="control_padding">12dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 6c8f8f39646e..4a31f3d9cf90 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -124,7 +124,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private float mScale; /** - * MagnificationFrame represents the bound of {@link #mMirrorSurface} and is constrained + * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained * by the {@link #mMagnificationFrameBoundary}. * We use MagnificationFrame to calculate the position of {@link #mMirrorView}. * We combine MagnificationFrame with {@link #mMagnificationFrameOffsetX} and @@ -225,6 +225,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private boolean mAllowDiagonalScrolling = false; private boolean mEditSizeEnable = false; private boolean mSettingsPanelVisibility = false; + @VisibleForTesting + WindowMagnificationSizePrefs mWindowMagnificationSizePrefs; @Nullable private final MirrorWindowControl mMirrorWindowControl; @@ -249,6 +251,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mWindowMagnifierCallback = callback; mSysUiState = sysUiState; mConfiguration = new Configuration(context.getResources().getConfiguration()); + mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext); final Display display = mContext.getDisplay(); mDisplayId = mContext.getDisplayId(); @@ -272,8 +275,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold com.android.internal.R.integer.config_shortAnimTime); updateDimensions(); - final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds); - setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(), + final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible(); + setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(), mWindowBounds.width() / 2, mWindowBounds.height() / 2); computeBounceAnimationScale(); @@ -381,12 +384,16 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (!mMagnificationSizeScaleOptions.contains(index)) { return; } - final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f); - final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3; - int size = (int) (initSize * scale); + int size = getMagnificationWindowSizeFromIndex(index); setWindowSize(size, size); } + int getMagnificationWindowSizeFromIndex(@MagnificationSize int index) { + final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f); + int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3; + return (int) (initSize * scale) - (int) (initSize * scale) % 2; + } + void setEditMagnifierSizeMode(boolean enable) { mEditSizeEnable = enable; applyResourcesValues(); @@ -395,6 +402,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold updateDimensions(); applyTapExcludeRegion(); } + + if (!enable) { + // Keep the magnifier size when exiting edit mode + mWindowMagnificationSizePrefs.saveSizeForCurrentDensity( + new Size(mMagnificationFrame.width(), mMagnificationFrame.height())); + } } void setDiagonalScrolling(boolean enable) { @@ -519,12 +532,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return false; } mWindowBounds.set(currentWindowBounds); - final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds); + final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible(); final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width(); final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height(); - setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(), (int) newCenterX, - (int) newCenterY); + setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(), + (int) newCenterX, (int) newCenterY); calculateMagnificationFrameBoundary(); return true; } @@ -738,6 +751,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } private void setMagnificationFrame(int width, int height, int centerX, int centerY) { + mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(new Size(width, height)); + // Sets the initial frame area for the mirror and place it to the given center on the // display. final int initX = centerX - width / 2; @@ -745,12 +760,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMagnificationFrame.set(initX, initY, initX + width, initY + height); } - private Size getDefaultWindowSizeWithWindowBounds(Rect windowBounds) { - int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2; - initSize = Math.min(mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size), - initSize); - initSize += 2 * mMirrorSurfaceMargin; - return new Size(initSize, initSize); + private Size restoreMagnificationWindowFrameSizeIfPossible() { + if (!mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) { + return getDefaultMagnificationWindowFrameSize(); + } + + return mWindowMagnificationSizePrefs.getSizeForCurrentDensity(); + } + + private Size getDefaultMagnificationWindowFrameSize() { + final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM) + - 2 * mMirrorSurfaceMargin; + return new Size(defaultSize, defaultSize); } /** diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java new file mode 100644 index 000000000000..4d7ad2647c1f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Size; + +/** + * Class to handle SharedPreference for window magnification size. + */ +public final class WindowMagnificationSizePrefs { + + private static final String WINDOW_MAGNIFICATION_PREFERENCES = + "window_magnification_preferences"; + Context mContext; + SharedPreferences mWindowMagnificationSizePreferences; + + public WindowMagnificationSizePrefs(Context context) { + mContext = context; + mWindowMagnificationSizePreferences = mContext + .getSharedPreferences(WINDOW_MAGNIFICATION_PREFERENCES, Context.MODE_PRIVATE); + } + + /** + * Uses smallest screen width DP as the key for preference. + */ + private String getKey() { + return String.valueOf( + mContext.getResources().getConfiguration().smallestScreenWidthDp); + } + + /** + * Saves the window frame size for current screen density. + */ + public void saveSizeForCurrentDensity(Size size) { + mWindowMagnificationSizePreferences.edit() + .putString(getKey(), size.toString()).apply(); + } + + /** + * Check if there is a preference saved for current screen density. + * + * @return true if there is a preference saved for current screen density, false if it is unset. + */ + public boolean isPreferenceSavedForCurrentDensity() { + return mWindowMagnificationSizePreferences.contains(getKey()); + } + + /** + * Gets the size preference for current screen density. + */ + public Size getSizeForCurrentDensity() { + return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null)); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt index e60d4e10f957..0c7d56f46530 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt @@ -100,6 +100,9 @@ class AuthBiometricFaceIconController( ) } else if (newState == STATE_ERROR && oldState != STATE_ERROR) { animateIconOnce(R.drawable.face_dialog_dark_to_error) + iconView.contentDescription = context.getString( + R.string.keyguard_face_failed + ) } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) iconView.contentDescription = context.getString( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java index 4b17be3c45d4..dc874d83b75b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java @@ -135,6 +135,9 @@ public class BiometricNotificationService implements CoreStartable { intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG); mContext.registerReceiver(mBroadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED_UNAUDITED); + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_NOT_REQUIRED, + UserHandle.USER_CURRENT); } private void queueFaceReenrollNotification() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index ebff0b05a52d..39a45f7f60a2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -26,6 +26,7 @@ import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROL import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import android.content.BroadcastReceiver; import android.content.Context; @@ -50,6 +51,7 @@ import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -234,6 +236,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { public static final VibrationEffect EFFECT_CLICK = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + public static final int LONG_PRESS = HapticFeedbackConstants.LONG_PRESS; + private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { @@ -926,12 +930,24 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting public void playStartHaptic() { if (mAccessibilityManager.isTouchExplorationEnabled()) { - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - EFFECT_CLICK, - "udfps-onStart-click", - UDFPS_VIBRATION_ATTRIBUTES); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mOverlay != null && mOverlay.getOverlayView() != null) { + mVibrator.performHapticFeedback( + mOverlay.getOverlayView(), + HapticFeedbackConstants.CONTEXT_CLICK + ); + } else { + Log.e(TAG, "No haptics played. Could not obtain overlay view to perform" + + "vibration. Either the controller overlay is null or has no view"); + } + } else { + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + EFFECT_CLICK, + "udfps-onStart-click", + UDFPS_VIBRATION_ATTRIBUTES); + } } } @@ -1024,12 +1040,24 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardViewManager.showPrimaryBouncer(true); // play the same haptic as the LockIconViewController longpress - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "aod-lock-icon-longpress", - LOCK_ICON_VIBRATION_ATTRIBUTES); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mOverlay != null && mOverlay.getOverlayView() != null) { + mVibrator.performHapticFeedback( + mOverlay.getOverlayView(), + UdfpsController.LONG_PRESS + ); + } else { + Log.e(TAG, "No haptics played. Could not obtain overlay view to perform" + + "vibration. Either the controller overlay is null or has no view"); + } + } else { + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "aod-lock-icon-longpress", + LOCK_ICON_VIBRATION_ATTRIBUTES); + } return; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt index a3f34ce7471d..eca0ada9e436 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt @@ -53,7 +53,6 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : subtitleView = requireViewById(R.id.subtitle) descriptionView = requireViewById(R.id.description) iconView = requireViewById(R.id.icon) - subtitleView = requireViewById(R.id.subtitle) passwordField = requireViewById(R.id.lockPassword) credentialHeader = requireViewById(R.id.auth_credential_header) credentialInput = requireViewById(R.id.auth_credential_input) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 9bbf1ef04481..7b78761af02b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -26,6 +26,7 @@ import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.util.Log import android.view.View +import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO import android.view.accessibility.AccessibilityManager import android.widget.Button import android.widget.TextView @@ -92,9 +93,11 @@ object BiometricViewBinder { val subtitleView = view.findViewById<TextView>(R.id.subtitle) val descriptionView = view.findViewById<TextView>(R.id.description) - // set selected for marquee - titleView.isSelected = true - subtitleView.isSelected = true + // set selected to enable marquee unless a screen reader is enabled + titleView.isSelected = + !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled + subtitleView.isSelected = + !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled descriptionView.movementMethod = ScrollingMovementMethod() val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay) @@ -335,6 +338,13 @@ object BiometricViewBinder { // dismiss prompt when authenticated and confirmed launch { viewModel.isAuthenticated.collect { authState -> + // Disable background view for cancelling authentication once authenticated, + // and remove from talkback + if (authState.isAuthenticated) { + backgroundView.setOnClickListener(null) + backgroundView.importantForAccessibility = + IMPORTANT_FOR_ACCESSIBILITY_NO + } if (authState.isAuthenticatedAndConfirmed) { view.announceForAccessibility( view.resources.getString(R.string.biometric_dialog_authenticated) diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java index 9c3448b91b39..dc32a59d86a9 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java @@ -16,34 +16,36 @@ package com.android.systemui.complication; -import static com.android.systemui.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW; +import static com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW; import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS; import android.view.View; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; +import com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent; import com.android.systemui.dagger.qualifiers.SystemUser; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.ViewController; import com.android.systemui.util.condition.ConditionalCoreStartable; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Provider; /** * Clock Time Complication that produce Clock Time view holder. */ public class DreamClockTimeComplication implements Complication { - private final Provider<DreamClockTimeViewHolder> mDreamClockTimeViewHolderProvider; + private final DreamClockTimeComplicationComponent.Factory mComponentFactory; /** * Default constructor for {@link DreamClockTimeComplication}. */ @Inject public DreamClockTimeComplication( - Provider<DreamClockTimeViewHolder> dreamClockTimeViewHolderProvider) { - mDreamClockTimeViewHolderProvider = dreamClockTimeViewHolderProvider; + DreamClockTimeComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; } @Override @@ -56,7 +58,7 @@ public class DreamClockTimeComplication implements Complication { */ @Override public ViewHolder createView(ComplicationViewModel model) { - return mDreamClockTimeViewHolderProvider.get(); + return mComponentFactory.create().getViewHolder(); } /** @@ -94,11 +96,14 @@ public class DreamClockTimeComplication implements Complication { private final ComplicationLayoutParams mLayoutParams; @Inject - DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view, + DreamClockTimeViewHolder( + @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view, @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS) - ComplicationLayoutParams layoutParams) { + ComplicationLayoutParams layoutParams, + DreamClockTimeViewController viewController) { mView = view; mLayoutParams = layoutParams; + viewController.init(); } @Override @@ -111,4 +116,29 @@ public class DreamClockTimeComplication implements Complication { return mLayoutParams; } } + + static class DreamClockTimeViewController extends ViewController<View> { + private final UiEventLogger mUiEventLogger; + + @Inject + DreamClockTimeViewController( + @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view, + UiEventLogger uiEventLogger) { + super(view); + + mUiEventLogger = uiEventLogger; + } + + @Override + protected void onViewAttached() { + mView.setOnClickListener(this::onClick); + } + + @Override + protected void onViewDetached() {} + + private void onClick(View v) { + mUiEventLogger.log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index 4d99282e6f8c..7ac1cc796d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -29,8 +29,6 @@ import android.util.Log; import android.view.View; import android.widget.ImageView; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -201,23 +199,6 @@ public class DreamHomeControlsComplication implements Complication { private final UiEventLogger mUiEventLogger; - @VisibleForTesting - public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum { - @UiEvent(doc = "The home controls on the screensaver has been tapped.") - DREAM_HOME_CONTROLS_TAPPED(1212); - - private final int mId; - - DreamOverlayEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } - } - @Inject DreamHomeControlsChipViewController( @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, @@ -246,7 +227,7 @@ public class DreamHomeControlsComplication implements Complication { private void onClickHomeControls(View v) { if (DEBUG) Log.d(TAG, "home controls complication tapped"); - mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED); + mUiEventLogger.log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED); final Intent intent = new Intent(mContext, ControlsActivity.class) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt new file mode 100644 index 000000000000..17cc8299c309 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.complication + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger.UiEventEnum + +/** UI log events for the dream overlay. */ +enum class DreamOverlayUiEvent(private val mId: Int) : UiEventEnum { + @UiEvent(doc = "The home controls on the screensaver has been tapped.") + DREAM_HOME_CONTROLS_TAPPED(1212), + @UiEvent(doc = "The clock on the screensaver has been tapped") DREAM_CLOCK_TAPPED(1440), + @UiEvent(doc = "The weather on the screensaver has been tapped") DREAM_WEATHER_TAPPED(1441); + + override fun getId(): Int { + return mId + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt new file mode 100644 index 000000000000..87c3b2f4117b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.complication.dagger + +import android.view.LayoutInflater +import android.view.View +import android.widget.TextClock +import com.android.internal.util.Preconditions +import com.android.systemui.R +import com.android.systemui.complication.DreamClockTimeComplication +import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder +import dagger.Module +import dagger.Provides +import dagger.Subcomponent +import javax.inject.Named +import javax.inject.Scope + +/** Responsible for generating dependencies for the [DreamClockTimeComplication]. */ +@Subcomponent( + modules = [DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule::class] +) +@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope +interface DreamClockTimeComplicationComponent { + /** Scope of the clock complication. */ + @MustBeDocumented + @Retention(AnnotationRetention.RUNTIME) + @Scope + annotation class DreamClockTimeComplicationScope + + /** Factory that generates a component for the clock complication. */ + @Subcomponent.Factory + interface Factory { + fun create(): DreamClockTimeComplicationComponent + } + + /** Creates a view holder for the clock complication. */ + fun getViewHolder(): DreamClockTimeViewHolder + + /** Module for providing injected values within the clock complication scope. */ + @Module + interface DreamClockTimeComplicationModule { + companion object { + const val DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view" + private const val TAG_WEIGHT = "'wght' " + private const val WEIGHT = 400 + + /** Provides the complication view. */ + @Provides + @DreamClockTimeComplicationScope + @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) + fun provideComplicationView(layoutInflater: LayoutInflater): View { + val view = + Preconditions.checkNotNull( + layoutInflater.inflate( + R.layout.dream_overlay_complication_clock_time, + /* root = */ null, + /* attachToRoot = */ false, + ) as TextClock, + "R.layout.dream_overlay_complication_clock_time did not properly inflate" + ) + view.setFontVariationSettings(TAG_WEIGHT + WEIGHT) + return view + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java deleted file mode 100644 index fd711eed53b8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.complication.dagger; - - -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextClock; - -import com.android.internal.util.Preconditions; -import com.android.systemui.R; -import com.android.systemui.complication.DreamClockTimeComplication; - -import dagger.Module; -import dagger.Provides; - -import javax.inject.Named; - -/** - * Module for providing {@link DreamClockTimeComplication}. - */ -@Module -public interface DreamClockTimeComplicationModule { - String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view"; - String TAG_WEIGHT = "'wght' "; - int WEIGHT = 400; - - /** - * Provides the complication view. - */ - @Provides - @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) - static View provideComplicationView(LayoutInflater layoutInflater) { - final View view = Preconditions.checkNotNull( - layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time, - null, false), - "R.layout.dream_overlay_complication_clock_time did not properly inflated"); - ((TextClock) view.findViewById(R.id.time_view)).setFontVariationSettings( - TAG_WEIGHT + WEIGHT); - return view; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java index 98975fbdfce2..776c9721aea5 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java @@ -35,10 +35,9 @@ import javax.inject.Named; * Module for all components with corresponding dream layer complications registered in * {@link SystemUIBinder}. */ -@Module(includes = { - DreamClockTimeComplicationModule.class, - }, +@Module( subcomponents = { + DreamClockTimeComplicationComponent.class, DreamHomeControlsComplicationComponent.class, DreamMediaEntryComplicationComponent.class }) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index b387e4a66bd2..4c9dbe02fad8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -40,8 +40,6 @@ import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import java.util.concurrent.Executor import javax.inject.Inject @@ -50,7 +48,6 @@ import javax.inject.Inject * Activity for rearranging and removing controls for a given structure */ open class ControlsEditingActivity @Inject constructor( - featureFlags: FeatureFlags, @Main private val mainExecutor: Executor, private val controller: ControlsControllerImpl, private val userTracker: UserTracker, @@ -76,8 +73,6 @@ open class ControlsEditingActivity @Inject constructor( private var isFromFavoriting: Boolean = false - private val isNewFlowEnabled: Boolean = - featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback { private val startingUser = controller.currentUserId @@ -176,7 +171,7 @@ open class ControlsEditingActivity @Inject constructor( private fun bindButtons() { addControls = requireViewById<Button>(R.id.addControls).apply { isEnabled = true - visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE + visibility = View.VISIBLE setOnClickListener { if (saveButton.isEnabled) { // The user has made changes diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 59fa7f53fc17..23721c93508b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -33,7 +33,6 @@ import android.view.ViewStub import android.widget.Button import android.widget.FrameLayout import android.widget.TextView -import android.widget.Toast import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import androidx.activity.ComponentActivity @@ -41,24 +40,19 @@ import androidx.annotation.VisibleForTesting import androidx.viewpager2.widget.ViewPager2 import com.android.systemui.Prefs import com.android.systemui.R -import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import java.text.Collator import java.util.concurrent.Executor import javax.inject.Inject open class ControlsFavoritingActivity @Inject constructor( - featureFlags: FeatureFlags, @Main private val executor: Executor, private val controller: ControlsControllerImpl, - private val listingController: ControlsListingController, private val userTracker: UserTracker, ) : ComponentActivity() { @@ -92,7 +86,6 @@ open class ControlsFavoritingActivity @Inject constructor( private lateinit var pageIndicator: ManagementPageIndicator private var mTooltipManager: TooltipManager? = null private lateinit var doneButton: View - private lateinit var otherAppsButton: View private lateinit var rearrangeButton: Button private var listOfStructures = emptyList<StructureContainer>() @@ -104,8 +97,6 @@ open class ControlsFavoritingActivity @Inject constructor( get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR private val fromEditing: Boolean get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING - private val isNewFlowEnabled: Boolean = - featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback { private val startingUser = controller.currentUserId @@ -124,20 +115,6 @@ open class ControlsFavoritingActivity @Inject constructor( onBackPressed() } - private val listingCallback = object : ControlsListingController.ControlsListingCallback { - - override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { - if (serviceInfos.size > 1) { - val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE - if (otherAppsButton.visibility != newVisibility) { - otherAppsButton.post { - otherAppsButton.visibility = newVisibility - } - } - } - } - } - override fun onBackPressed() { if (fromEditing) { animateExitAndFinish() @@ -342,7 +319,7 @@ open class ControlsFavoritingActivity @Inject constructor( getString(R.string.controls_favorite_rearrange_button) } isEnabled = false - visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE + visibility = View.VISIBLE setOnClickListener { if (component == null) return@setOnClickListener saveFavorites() @@ -361,24 +338,6 @@ open class ControlsFavoritingActivity @Inject constructor( ) } } - otherAppsButton = requireViewById<Button>(R.id.other_apps).apply { - setOnClickListener { - if (doneButton.isEnabled) { - // The user has made changes - Toast.makeText( - applicationContext, - R.string.controls_favorite_toast_no_changes, - Toast.LENGTH_SHORT - ).show() - } - startActivity( - Intent(context, ControlsProviderSelectorActivity::class.java), - ActivityOptions - .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle() - ) - animateExitAndFinish() - } - } doneButton = requireViewById<Button>(R.id.done).apply { isEnabled = false @@ -415,7 +374,6 @@ open class ControlsFavoritingActivity @Inject constructor( override fun onStart() { super.onStart() - listingController.addCallback(listingCallback) userTracker.addCallback(userTrackerCallback, executor) if (DEBUG) { @@ -440,7 +398,6 @@ open class ControlsFavoritingActivity @Inject constructor( override fun onStop() { super.onStop() - listingController.removeCallback(listingCallback) userTracker.removeCallback(userTrackerCallback) if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt index 5c2402ba4149..4aef20928bb8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt @@ -20,8 +20,6 @@ package com.android.systemui.controls.panels import android.content.Context import android.content.SharedPreferences import com.android.systemui.R -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl @@ -33,7 +31,6 @@ constructor( private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, - private val featureFlags: FeatureFlags, ) : AuthorizedPanelsRepository { override fun getAuthorizedPanels(): Set<String> { @@ -74,17 +71,8 @@ constructor( userTracker.userId, ) - // We should add default packages in two cases: - // 1) We've never run this - // 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps - val needToSetup = - if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) { - sharedPref.getStringSet(KEY, null) == null - } else { - // There might be an empty set that need to be overridden after the feature has been - // turned off after being turned on - sharedPref.getStringSet(KEY, null).isNullOrEmpty() - } + // We should add default packages when we've never run this + val needToSetup = sharedPref.getStringSet(KEY, null) == null if (needToSetup) { sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt index 0fb5b66ef93c..c9edd4ac7426 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.SharedPreferences import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl @@ -83,11 +82,7 @@ constructor( } override fun shouldAddDefaultComponent(): Boolean = - if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) { - sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true) - } else { - true - } + sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true) override fun setShouldAddDefaultComponent(shouldAdd: Boolean) { sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 25eae200e950..3cdf9ab69942 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -72,7 +72,6 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -506,32 +505,22 @@ class ControlsUiControllerImpl @Inject constructor ( val isPanel = selectedItem is SelectedItem.PanelItem val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure ?: EMPTY_STRUCTURE - val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS) val items = buildList { add(OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_open_app), OPEN_APP_ID )) - if (newFlows || isPanel) { - if (extraApps) { - add(OverflowMenuAdapter.MenuItem( - context.getText(R.string.controls_menu_add_another_app), - ADD_APP_ID - )) - } - if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) { - add(OverflowMenuAdapter.MenuItem( - context.getText(R.string.controls_menu_remove), - REMOVE_APP_ID, - )) - } - } else { + if (extraApps) { add(OverflowMenuAdapter.MenuItem( - context.getText(R.string.controls_menu_add), - ADD_CONTROLS_ID + context.getText(R.string.controls_menu_add_another_app), + ADD_APP_ID )) } + add(OverflowMenuAdapter.MenuItem( + context.getText(R.string.controls_menu_remove), + REMOVE_APP_ID, + )) if (!isPanel) { add(OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_menu_edit), @@ -665,7 +654,7 @@ class ControlsUiControllerImpl @Inject constructor ( val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources) - val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup + val listView = parent.requireViewById(R.id.controls_list) as ViewGroup listView.removeAllViews() var lastRow: ViewGroup = createRow(inflater, listView) selectedStructure.controls.forEach { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7e4f501e3c27..d34ba1fc5eab 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -195,10 +195,10 @@ object Flags { @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded") /** Enables code to show contextual loyalty cards in wallet entrypoints */ - // TODO(b/247587924): Tracking Bug + // TODO(b/294110497): Tracking Bug @JvmField val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS = - unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false) + unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = true) // TODO(b/242908637): Tracking Bug @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview") @@ -617,7 +617,8 @@ object Flags { val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true) // TODO(b/279405451): Tracking Bug @JvmField - val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions") + val CLIPBOARD_SHARED_TRANSITIONS = + unreleasedFlag(1703, "clipboard_shared_transitions", teamfood = true) // TODO(b/283300105): Tracking Bug @JvmField val SCENE_CONTAINER = unreleasedFlag(1802, "scene_container") @@ -628,14 +629,6 @@ object Flags { // 2000 - device controls @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed") - @JvmField - val CONTROLS_MANAGEMENT_NEW_FLOWS = releasedFlag(2002, "controls_management_new_flows") - - // Enables removing app from Home control panel as a part of a new flow - // TODO(b/269132640): Tracking Bug - @JvmField - val APP_PANELS_REMOVE_APPS_ALLOWED = releasedFlag(2003, "app_panels_remove_apps_allowed") - // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.) // TODO(b/259264861): Tracking Bug @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection") diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java index de67ba8a6ded..6d083f6c8bfd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java @@ -87,7 +87,7 @@ public class GlobalActionsPopupMenu extends ListPopupWindow { if (mIsDropDownMode) { // use a divider listView.setDividerHeight(res.getDimensionPixelSize(R.dimen.control_list_divider)); - listView.setDivider(res.getDrawable(R.drawable.controls_list_divider_inset)); + listView.setDivider(res.getDrawable(R.drawable.global_actions_list_divider_inset)); } else { if (mAdapter == null) return; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2686233a436d..66de371ab6e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -3291,7 +3291,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, flags |= StatusBarManager.DISABLE_RECENT; } - if (mPowerGestureIntercepted && mOccluded && isSecure()) { + if (mPowerGestureIntercepted && mOccluded && isSecure() + && mUpdateMonitor.isFaceEnrolled()) { flags |= StatusBarManager.DISABLE_RECENT; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 2085c877064d..888f74601ebd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -24,9 +24,11 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @SysUISingleton @@ -43,19 +45,27 @@ constructor( ) { override fun start() { - listenForAodToLockscreen() + listenForAodToLockscreenOrOccluded() listenForAodToGone() } - private fun listenForAodToLockscreen() { + private fun listenForAodToLockscreenOrOccluded() { scope.launch { keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) - .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { pair -> - val (dozeToAod, lastStartedStep) = pair + .sample( + combine( + transitionInteractor.startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardOccluded, + ::Pair + ), + ::toTriple + ) + .collect { (_, lastStartedStep, occluded) -> if (lastStartedStep.to == KeyguardState.AOD) { - startTransitionTo(KeyguardState.LOCKSCREEN) + startTransitionTo( + if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index c867c43b1646..76d9893a8927 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -23,10 +23,12 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @SysUISingleton @@ -43,20 +45,29 @@ constructor( ) { override fun start() { - listenForDozingToLockscreen() + listenForDozingToLockscreenOrOccluded() listenForDozingToGone() } - private fun listenForDozingToLockscreen() { + private fun listenForDozingToLockscreenOrOccluded() { scope.launch { keyguardInteractor.wakefulnessModel - .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { (wakefulnessModel, lastStartedTransition) -> + .sample( + combine( + transitionInteractor.startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardOccluded, + ::Pair + ), + ::toTriple + ) + .collect { (wakefulnessModel, lastStartedTransition, occluded) -> if ( wakefulnessModel.isStartingToWakeOrAwake() && lastStartedTransition.to == KeyguardState.DOZING ) { - startTransitionTo(KeyguardState.LOCKSCREEN) + startTransitionTo( + if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index 44e1fd157cf9..cca96b7830c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -66,6 +66,23 @@ constructor( }, ) + /** Lockscreen alpha */ + val lockscreenAlpha: Flow<Float> = + transitionAnimation.createFlow( + duration = 50.milliseconds, + onStart = { + leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() + willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard() + }, + onStep = { + if (willRunDismissFromKeyguard || leaveShadeOpen) { + 1f + } else { + 0f + } + }, + ) + /** Scrim alpha values */ val scrimAlpha: Flow<ScrimAlpha> = transitionAnimation diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 6d9844d9ec59..68202d5629a0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -57,7 +57,7 @@ import javax.inject.Inject; @SysUISingleton public class RingtonePlayer implements CoreStartable { private static final String TAG = "RingtonePlayer"; - private static final boolean LOGD = false; + private static final boolean LOGD = true; private final Context mContext; // TODO: support Uri switching under same IBinder @@ -111,9 +111,53 @@ public class RingtonePlayer implements CoreStartable { @Override public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping) throws RemoteException { - playRemoteRingtone(token, uri, aa, true, Ringtone.MEDIA_SOUND, - null, volume, looping, /* hapticGenerator= */ false, - null); + if (Ringtone.useRingtoneV2()) { + playRemoteRingtone(token, uri, aa, true, Ringtone.MEDIA_SOUND, + null, volume, looping, /* hapticGenerator= */ false, + null); + } else { + playWithVolumeShaping(token, uri, aa, volume, looping, null); + } + } + + @Override + public void playWithVolumeShaping( + IBinder token, Uri uri, AudioAttributes aa, float volume, + boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig) + throws RemoteException { + if (LOGD) { + Log.d(TAG, "playWithVolumeShaping(token=" + token + ", uri=" + uri + ", uid=" + + Binder.getCallingUid() + ")"); + } + Client client; + synchronized (mClients) { + client = mClients.get(token); + } + // Don't hold the lock while constructing the ringtone, since it can be slow. The caller + // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and + // waste the build. + if (client == null) { + final UserHandle user = Binder.getCallingUserHandle(); + Ringtone ringtone = Ringtone.createV1WithCustomAudioAttributes( + getContextForUser(user), aa, uri, volumeShaperConfig, + /* allowRemote= */ false); + synchronized (mClients) { + client = mClients.get(token); + if (client == null) { + client = new Client(token, ringtone); + token.linkToDeath(client, 0); + mClients.put(token, client); + ringtone = null; // "owned" by the client now. + } + } + // Clean up ringtone if it was abandoned (a client already existed). + if (ringtone != null) { + ringtone.stop(); + } + } + client.mRingtone.setLooping(looping); + client.mRingtone.setVolume(volume); + client.mRingtone.play(); } @Override @@ -125,7 +169,7 @@ public class RingtonePlayer implements CoreStartable { @Nullable VolumeShaper.Configuration volumeShaperConfig) throws RemoteException { if (LOGD) { - Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid=" + Log.d(TAG, "playRemoteRingtone(token=" + token + ", uri=" + uri + ", uid=" + Binder.getCallingUid() + ")"); } @@ -190,6 +234,21 @@ public class RingtonePlayer implements CoreStartable { return false; } } + @Override + public void setPlaybackProperties(IBinder token, float volume, boolean looping, + boolean hapticGeneratorEnabled) { + // RingtoneV1-exclusive path. + Client client; + synchronized (mClients) { + client = mClients.get(token); + } + if (client != null) { + client.mRingtone.setVolume(volume); + client.mRingtone.setLooping(looping); + client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled); + } + // else no client for token when setting playback properties but will be set at play() + } @Override public void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 2adc211ef23f..0842fe0dd764 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -31,6 +31,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ShortcutManager import android.graphics.drawable.Icon +import android.os.Process import android.os.UserHandle import android.os.UserManager import android.provider.Settings @@ -317,7 +318,9 @@ constructor( return } - if (user == userTracker.userHandle) { + // When switched to a secondary user, the sysUI is still running in the main user, we will + // need to update the shortcut in the secondary user. + if (user == getCurrentRunningUser()) { updateNoteTaskAsUserInternal(user) } else { // TODO(b/278729185): Replace fire and forget service with a bounded service. @@ -354,6 +357,9 @@ constructor( updateNoteTaskAsUser(user) } + // Returns the [UserHandle] that this class is running on. + @VisibleForTesting internal fun getCurrentRunningUser(): UserHandle = Process.myUserHandle() + private val SecureSettings.preferredUser: UserHandle get() { val trackingUserId = userTracker.userHandle.identifier diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 3e7bdd1a1444..9bb192b4c271 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -34,6 +34,7 @@ import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.Switch; @@ -315,6 +316,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { public void onZenChanged(int zen) { + Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile."); refreshState(zen); } }; diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 1fca4886a31f..fee3960ff0e1 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -14,16 +14,23 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.scene.data.repository +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf /** Source of truth for scene framework application state. */ class SceneContainerRepository @@ -38,8 +45,17 @@ constructor( private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey)) val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow() - private val _transitionProgress = MutableStateFlow(1f) - val transitionProgress: StateFlow<Float> = _transitionProgress.asStateFlow() + private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) + val transitionProgress: Flow<Float> = + transitionState.flatMapLatest { observableTransitionStateFlow -> + observableTransitionStateFlow?.flatMapLatest { observableTransitionState -> + when (observableTransitionState) { + is ObservableTransitionState.Idle -> flowOf(1f) + is ObservableTransitionState.Transition -> observableTransitionState.progress + } + } + ?: flowOf(1f) + } private val _transitions = MutableStateFlow<SceneTransitionModel?>(null) val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow() @@ -92,8 +108,12 @@ constructor( _isVisible.value = isVisible } - /** Sets scene transition progress to the current scene in the container with the given name. */ - fun setSceneTransitionProgress(progress: Float) { - _transitionProgress.value = progress + /** + * Binds the given flow so the system remembers it. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { + this.transitionState.value = transitionState } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 39daad33f75e..b09a5cf087cb 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -18,11 +18,13 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.data.repository.SceneContainerRepository +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -69,13 +71,17 @@ constructor( /** Whether the container with the given name is visible. */ val isVisible: StateFlow<Boolean> = repository.isVisible - /** Sets scene transition progress to the current scene in the container with the given name. */ - fun setSceneTransitionProgress(progress: Float) { - repository.setSceneTransitionProgress(progress) + /** + * Binds the given flow so the system remembers it. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { + repository.setTransitionState(transitionState) } /** Progress of the transition into the current scene in the container with the given name. */ - val transitionProgress: StateFlow<Float> = repository.transitionProgress + val transitionProgress: Flow<Float> = repository.transitionProgress /** * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt new file mode 100644 index 000000000000..9a30aa65068c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt @@ -0,0 +1,36 @@ +/* + * 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.systemui.scene.shared.model + +import kotlinx.coroutines.flow.Flow + +/** + * This is a fork of a class by the same name in the `com.android.compose.animation.scene` package. + * + * TODO(b/293899074): remove this fork, once we can compile Compose into System UI. + */ +sealed class ObservableTransitionState { + /** No transition/animation is currently running. */ + data class Idle(val scene: SceneKey) : ObservableTransitionState() + + /** There is a transition animating between two scenes. */ + data class Transition( + val fromScene: SceneKey, + val toScene: SceneKey, + val progress: Flow<Float>, + ) : ObservableTransitionState() +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index f44748a99080..bd73e36262b8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -19,10 +19,12 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.RemoteUserInput import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow /** Models UI state for the scene container. */ @@ -54,9 +56,13 @@ constructor( interactor.setCurrentScene(scene) } - /** Notifies of the progress of a scene transition. */ - fun setSceneTransitionProgress(progress: Float) { - interactor.setSceneTransitionProgress(progress) + /** + * Binds the given flow so the system remembers it. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { + interactor.setTransitionState(transitionState) } /** Handles a [MotionEvent] representing remote user input. */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 2ea63c2a9847..416f147b7429 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -147,6 +147,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; import com.android.systemui.media.controls.ui.MediaHierarchyManager; @@ -602,6 +603,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mGoneToDreamingLockscreenHostedTransitionViewModel; private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; + private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final KeyguardInteractor mKeyguardInteractor; @@ -761,6 +763,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump GoneToDreamingLockscreenHostedTransitionViewModel goneToDreamingLockscreenHostedTransitionViewModel, LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, + PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager, @@ -790,6 +793,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mGoneToDreamingLockscreenHostedTransitionViewModel = goneToDreamingLockscreenHostedTransitionViewModel; mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; + mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mKeyguardInteractor = keyguardInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; @@ -1172,6 +1176,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY( mLockscreenToOccludedTransitionTranslationY), setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); + + // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth) + collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 80f5d1939ac0..a4e8c2ece894 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -21,16 +21,12 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; -import android.net.Uri; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; @@ -75,10 +71,6 @@ import javax.inject.Named; @NotificationRowScope public class ExpandableNotificationRowController implements NotifViewController { private static final String TAG = "NotifRowController"; - - static final Uri BUBBLES_SETTING_URI = - Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES); - private static final String BUBBLES_SETTING_ENABLED_VALUE = "1"; private final ExpandableNotificationRow mView; private final NotificationListContainer mListContainer; private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory; @@ -112,23 +104,6 @@ public class ExpandableNotificationRowController implements NotifViewController private final ExpandableNotificationRowDragController mDragController; private final NotificationDismissibilityProvider mDismissibilityProvider; private final IStatusBarService mStatusBarService; - - private final NotificationSettingsController mSettingsController; - - @VisibleForTesting - final NotificationSettingsController.Listener mSettingsListener = - new NotificationSettingsController.Listener() { - @Override - public void onSettingChanged(Uri setting, int userId, String value) { - if (BUBBLES_SETTING_URI.equals(setting)) { - final int viewUserId = mView.getEntry().getSbn().getUserId(); - if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) { - mView.getPrivateLayout().setBubblesEnabledForUser( - BUBBLES_SETTING_ENABLED_VALUE.equals(value)); - } - } - } - }; private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback = new ExpandableNotificationRow.ExpandableNotificationRowLogger() { @Override @@ -226,7 +201,6 @@ public class ExpandableNotificationRowController implements NotifViewController FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, - NotificationSettingsController settingsController, ExpandableNotificationRowDragController dragController, NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService) { @@ -255,7 +229,6 @@ public class ExpandableNotificationRowController implements NotifViewController mFeatureFlags = featureFlags; mPeopleNotificationIdentifier = peopleNotificationIdentifier; mBubblesManagerOptional = bubblesManagerOptional; - mSettingsController = settingsController; mDragController = dragController; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; @@ -325,14 +298,12 @@ public class ExpandableNotificationRowController implements NotifViewController NotificationMenuRowPlugin.class, false /* Allow multiple */); mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD); mStatusBarStateController.addCallback(mStatusBarStateListener); - mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener); } @Override public void onViewDetachedFromWindow(View v) { mPluginManager.removePluginListener(mView); mStatusBarStateController.removeCallback(mStatusBarStateListener); - mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 1657c2897ea4..03225730619e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -44,7 +44,6 @@ import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; @@ -66,6 +65,7 @@ import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt; import com.android.systemui.statusbar.policy.SmartReplyView; import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent; import com.android.systemui.util.Compile; +import com.android.systemui.wmshell.BubblesManager; import java.io.PrintWriter; import java.util.ArrayList; @@ -134,7 +134,6 @@ public class NotificationContentView extends FrameLayout implements Notification private PeopleNotificationIdentifier mPeopleIdentifier; private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory; private IStatusBarService mStatusBarService; - private boolean mBubblesEnabledForUser; /** * List of listeners for when content views become inactive (i.e. not the showing view). @@ -1441,17 +1440,12 @@ public class NotificationContentView extends FrameLayout implements Notification } } - @Background - public void setBubblesEnabledForUser(boolean enabled) { - mBubblesEnabledForUser = enabled; - } - @VisibleForTesting boolean shouldShowBubbleButton(NotificationEntry entry) { boolean isPersonWithShortcut = mPeopleIdentifier.getPeopleNotificationType(entry) >= PeopleNotificationIdentifier.TYPE_FULL_PERSON; - return mBubblesEnabledForUser + return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser()) && isPersonWithShortcut && entry.getBubbleMetadata() != null; } @@ -2085,7 +2079,6 @@ public class NotificationContentView extends FrameLayout implements Notification pw.print("null"); } pw.println(); - pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser); pw.print("RemoteInputViews { "); pw.print(" visibleType: " + mVisibleType); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java deleted file mode 100644 index 585ff523b9a0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.row; - -import android.content.Context; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.os.HandlerExecutor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.systemui.Dumpable; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.util.settings.SecureSettings; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; - -import javax.inject.Inject; - -/** - * Centralized controller for listening to Secure Settings changes and informing in-process - * listeners, on a background thread. - */ -@SysUISingleton -public class NotificationSettingsController implements Dumpable { - - private final static String TAG = "NotificationSettingsController"; - private final UserTracker mUserTracker; - private final UserTracker.Callback mCurrentUserTrackerCallback; - private final Handler mHandler; - private final ContentObserver mContentObserver; - private final SecureSettings mSecureSettings; - private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>(); - - @Inject - public NotificationSettingsController(UserTracker userTracker, - @Background Handler handler, - SecureSettings secureSettings, - DumpManager dumpManager) { - mUserTracker = userTracker; - mHandler = handler; - mSecureSettings = secureSettings; - mContentObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange, Uri uri) { - super.onChange(selfChange, uri); - synchronized (mListeners) { - if (mListeners.containsKey(uri)) { - for (Listener listener : mListeners.get(uri)) { - notifyListener(listener, uri); - } - } - } - } - }; - - mCurrentUserTrackerCallback = new UserTracker.Callback() { - @Override - public void onUserChanged(int newUser, Context userContext) { - synchronized (mListeners) { - if (mListeners.size() > 0) { - mSecureSettings.unregisterContentObserver(mContentObserver); - for (Uri uri : mListeners.keySet()) { - mSecureSettings.registerContentObserverForUser( - uri, false, mContentObserver, newUser); - } - } - } - } - }; - mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler)); - - dumpManager.registerNormalDumpable(TAG, this); - } - - /** - * Register callback whenever the given secure settings changes. - * - * On registration, will call back on the provided handler with the current value of - * the setting. - */ - public void addCallback(@NonNull Uri uri, @NonNull Listener listener) { - if (uri == null || listener == null) { - return; - } - synchronized (mListeners) { - ArrayList<Listener> currentListeners = mListeners.get(uri); - if (currentListeners == null) { - currentListeners = new ArrayList<>(); - } - if (!currentListeners.contains(listener)) { - currentListeners.add(listener); - } - mListeners.put(uri, currentListeners); - if (currentListeners.size() == 1) { - mSecureSettings.registerContentObserverForUser( - uri, false, mContentObserver, mUserTracker.getUserId()); - } - } - mHandler.post(() -> notifyListener(listener, uri)); - - } - - public void removeCallback(Uri uri, Listener listener) { - synchronized (mListeners) { - ArrayList<Listener> currentListeners = mListeners.get(uri); - - if (currentListeners != null) { - currentListeners.remove(listener); - } - if (currentListeners == null || currentListeners.size() == 0) { - mListeners.remove(uri); - } - - if (mListeners.size() == 0) { - mSecureSettings.unregisterContentObserver(mContentObserver); - } - } - } - - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - synchronized (mListeners) { - pw.println("Settings Uri Listener List:"); - for (Uri uri : mListeners.keySet()) { - pw.println(" Uri=" + uri); - for (Listener listener : mListeners.get(uri)) { - pw.println(" Listener=" + listener.getClass().getName()); - } - } - } - } - - private void notifyListener(Listener listener, Uri uri) { - final String setting = uri == null ? null : uri.getLastPathSegment(); - int userId = mUserTracker.getUserId(); - listener.onSettingChanged(uri, userId, mSecureSettings.getStringForUser(setting, userId)); - } - - /** - * Listener invoked whenever settings are changed. - */ - public interface Listener { - void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value); - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index 1c3a8850df8d..dabdcc5fc0f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -46,7 +46,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.Utils; import com.android.systemui.util.settings.GlobalSettings; @@ -68,17 +67,17 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { private final Context mContext; private final UserTracker mUserTracker; private final BroadcastDispatcher mBroadcastDispatcher; - private final SettingObserver mModeSetting; - private final SettingObserver mConfigSetting; private final NotificationManager mNoMan; private final AlarmManager mAlarmManager; private final SetupObserver mSetupObserver; private final UserManager mUserManager; + private final GlobalSettings mGlobalSettings; private int mUserId; private boolean mRegistered; private ZenModeConfig mConfig; - private int mZenMode; + // This value is changed in the main thread, but may be read in a background thread. + private volatile int mZenMode; private long mZenUpdateTime; private NotificationManager.Policy mConsolidatedNotificationPolicy; @@ -111,18 +110,20 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mUserTracker = userTracker; - mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE, - userTracker.getUserId()) { + mGlobalSettings = globalSettings; + + ContentObserver modeContentObserver = new ContentObserver(handler) { @Override - protected void handleValueChanged(int value, boolean observedChange) { + public void onChange(boolean selfChange) { + int value = getModeSettingValueFromProvider(); + Log.d(TAG, "Zen mode setting changed to " + value); updateZenMode(value); fireZenChanged(value); } }; - mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG, - userTracker.getUserId()) { + ContentObserver configContentObserver = new ContentObserver(handler) { @Override - protected void handleValueChanged(int value, boolean observedChange) { + public void onChange(boolean selfChange) { try { Trace.beginSection("updateZenModeConfig"); updateZenModeConfig(); @@ -132,9 +133,9 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - mModeSetting.setListening(true); - updateZenMode(mModeSetting.getValue()); - mConfigSetting.setListening(true); + globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); + updateZenMode(getModeSettingValueFromProvider()); + globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver); updateZenModeConfig(); updateConsolidatedNotificationPolicy(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); @@ -146,6 +147,10 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { dumpManager.registerDumpable(getClass().getSimpleName(), this); } + private int getModeSettingValueFromProvider() { + return mGlobalSettings.getInt(Global.ZEN_MODE, /* default */ Global.ZEN_MODE_OFF); + } + @Override public boolean isVolumeRestricted() { return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt index 1e73cb3b9b24..1e6556645afb 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt @@ -79,15 +79,15 @@ constructor( private val hapticsScale: Float get() { - val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1") - return intensityString.toFloatOrNull() ?: 0.1f + val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.5") + return intensityString.toFloatOrNull() ?: 0.5f } private val hapticsScaleTick: Float get() { val intensityString = - SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6") - return intensityString.toFloatOrNull() ?: 0.6f + SystemProperties.get("persist.unfold.haptics_scale_end_tick", "1.0") + return intensityString.toFloatOrNull() ?: 1.0f } private val primitivesCount: Int diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 5f0f7b8c223a..0e97e2192cdc 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -945,6 +945,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, showRingerDrawer(); } }); + updateSelectedRingerContainerDescription(mIsRingerDrawerOpen); mRingerDrawerVibrate.setOnClickListener( new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); @@ -1007,6 +1008,19 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, : 0; } + @VisibleForTesting String getSelectedRingerContainerDescription() { + return mSelectedRingerContainer == null ? null : + mSelectedRingerContainer.getContentDescription().toString(); + } + + @VisibleForTesting void toggleRingerDrawer(boolean show) { + if (show) { + showRingerDrawer(); + } else { + hideRingerDrawer(); + } + } + /** Animates in the ringer drawer. */ private void showRingerDrawer() { if (mIsRingerDrawerOpen) { @@ -1084,12 +1098,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .start(); } - // When the ringer drawer is open, tapping the currently selected ringer will set the ringer - // to the current ringer mode. Change the content description to that, instead of the 'tap - // to change ringer mode' default. - mSelectedRingerContainer.setContentDescription( - mContext.getString(getStringDescriptionResourceForRingerMode( - mState.ringerModeInternal))); + updateSelectedRingerContainerDescription(true); mIsRingerDrawerOpen = true; } @@ -1135,14 +1144,38 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .translationY(0f) .start(); - // When the drawer is closed, tapping the selected ringer drawer will open it, allowing the - // user to change the ringer. - mSelectedRingerContainer.setContentDescription( - mContext.getString(R.string.volume_ringer_change)); + updateSelectedRingerContainerDescription(false); mIsRingerDrawerOpen = false; } + + /** + * @param open false to set the description when drawer is closed + */ + private void updateSelectedRingerContainerDescription(boolean open) { + if (mState == null || mSelectedRingerContainer == null) return; + + String currentMode = mContext.getString(getStringDescriptionResourceForRingerMode( + mState.ringerModeInternal)); + String tapToSelect; + + if (open) { + // When the ringer drawer is open, tapping the currently selected ringer will set the + // ringer to the current ringer mode. Change the content description to that, instead of + // the 'tap to change ringer mode' default. + tapToSelect = ""; + + } else { + // When the drawer is closed, tapping the selected ringer drawer will open it, allowing + // the user to change the ringer. The user needs to know that, and also the current mode + currentMode += ", "; + tapToSelect = mContext.getString(R.string.volume_ringer_change); + } + + mSelectedRingerContainer.setContentDescription(currentMode + tapToSelect); + } + private void initSettingsH(int lockTaskModeState) { if (mSettingsView != null) { mSettingsView.setVisibility( @@ -1726,7 +1759,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, }); } - private int getStringDescriptionResourceForRingerMode(int mode) { + @VisibleForTesting int getStringDescriptionResourceForRingerMode(int mode) { switch (mode) { case RINGER_MODE_SILENT: return R.string.volume_ringer_status_silent; @@ -1825,6 +1858,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, updateVolumeRowH(row); } updateRingerH(); + updateSelectedRingerContainerDescription(mIsRingerDrawerOpen); mWindow.setTitle(composeWindowTitle()); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index 4da5d499c5e7..de9b5ee2e19a 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -157,9 +157,10 @@ public class QuickAccessWalletController { * Query the wallet cards from {@link QuickAccessWalletClient}. * * @param cardsRetriever a callback to retrieve wallet cards. + * @param maxCards the maximum number of cards requested from the QuickAccessWallet */ public void queryWalletCards( - QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) { + QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards) { if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis > RECREATION_TIME_WINDOW) { Log.i(TAG, "Re-creating the QAW client to avoid stale."); @@ -175,11 +176,22 @@ public class QuickAccessWalletController { mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height); int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); GetWalletCardsRequest request = - new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1); + new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, maxCards); mQuickAccessWalletClient.getWalletCards(mBgExecutor, request, cardsRetriever); } /** + * Query the wallet cards from {@link QuickAccessWalletClient}. + * + * @param cardsRetriever a callback to retrieve wallet cards. + */ + public void queryWalletCards( + QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) { + queryWalletCards(cardsRetriever, /* maxCards= */ 1); + } + + + /** * Re-create the {@link QuickAccessWalletClient} of the controller. */ public void reCreateWalletClient() { diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt index b3ad9b0c6a37..75df1bd2f680 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt @@ -39,7 +39,6 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn @@ -88,7 +87,7 @@ constructor( QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE ) walletController.updateWalletPreference() - walletController.queryWalletCards(callback) + walletController.queryWalletCards(callback, MAX_CARDS) awaitClose { walletController.unregisterWalletChangeObservers( @@ -152,5 +151,6 @@ constructor( companion object { private const val TAG = "WalletSuggestions" + private const val MAX_CARDS = 50 } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 11ad20672100..a48fa5da1970 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -71,6 +71,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; import android.text.TextUtils; +import android.util.Size; import android.view.Display; import android.view.IWindowSession; import android.view.MotionEvent; @@ -100,6 +101,7 @@ import com.google.common.util.concurrent.AtomicDouble; import org.junit.After; import org.junit.Assume; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -313,10 +315,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { assertFalse(rects.isEmpty()); } + @Ignore("The default window size should be constrained after fixing b/288056772") @Test public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() { - final int screenSize = mContext.getResources().getDimensionPixelSize( - R.dimen.magnification_max_frame_size) * 10; + final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10; mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); mInstrumentation.runOnMainSync(() -> { @@ -543,17 +545,22 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test - public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() { + public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() { // The default position is at the center of the screen. final float expectedRatio = 0.5f; - final Rect testWindowBounds = new Rect( - mWindowManager.getCurrentWindowMetrics().getBounds()); - testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, - testWindowBounds.right + 100, testWindowBounds.bottom + 100); + mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); + + // Screen size and density change + mContext.getResources().getConfiguration().smallestScreenWidthDp = + mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; + final Rect testWindowBounds = new Rect( + mWindowManager.getCurrentWindowMetrics().getBounds()); + testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, + testWindowBounds.right + 100, testWindowBounds.bottom + 100); mWindowManager.setWindowBounds(testWindowBounds); mInstrumentation.runOnMainSync(() -> { @@ -568,26 +575,49 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowMagnificationController.getCenterY() / testWindowBounds.height(), 0); } + + @Test + public void onScreenChangedToSavedDensity_enabled_restoreSavedMagnifierWindow() { + mContext.getResources().getConfiguration().smallestScreenWidthDp = + mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; + int windowFrameSize = mResources.getDimensionPixelSize( + com.android.internal.R.dimen.accessibility_window_magnifier_min_size); + mWindowMagnificationController.mWindowMagnificationSizePrefs.saveSizeForCurrentDensity( + new Size(windowFrameSize, windowFrameSize)); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, + Float.NaN); + }); + + WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); + assertTrue(params.width == windowFrameSize); + assertTrue(params.height == windowFrameSize); + } + @Test - public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() { + public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); - final int screenSize = mContext.getResources().getDimensionPixelSize( - R.dimen.magnification_max_frame_size) * 10; + final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10; + // Screen size and density change + mContext.getResources().getConfiguration().smallestScreenWidthDp = + mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize)); mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); }); - final int halfScreenSize = screenSize / 2; + final int defaultWindowSize = + mWindowMagnificationController.getMagnificationWindowSizeFromIndex( + WindowMagnificationSettings.MagnificationSize.MEDIUM); WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView(); - // The frame size should be the half of smaller value of window height/width unless it - //exceed the max frame size. - assertTrue(params.width < halfScreenSize); - assertTrue(params.height < halfScreenSize); + + assertTrue(params.width == defaultWindowSize); + assertTrue(params.height == defaultWindowSize); } @Test @@ -1136,6 +1166,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mWindowManager.setWindowInsets(testInsets); } + private int updateMirrorSurfaceMarginDimension() { + return mContext.getResources().getDimensionPixelSize( + R.dimen.magnification_mirror_surface_margin); + } + @Surface.Rotation private int simulateRotateTheDevice() { final Display display = Mockito.spy(mContext.getDisplay()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java new file mode 100644 index 000000000000..04b0d7085115 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Size; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class WindowMagnificationSizePrefsTest extends SysuiTestCase { + + WindowMagnificationSizePrefs mWindowMagnificationSizePrefs = + new WindowMagnificationSizePrefs(mContext); + + @Test + public void saveSizeForCurrentDensity_getExpectedSize() { + Size testSize = new Size(500, 500); + mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize); + + assertThat(mWindowMagnificationSizePrefs.getSizeForCurrentDensity()) + .isEqualTo(testSize); + } + + @Test + public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() { + Size testSize = new Size(500, 500); + mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize); + + assertThat(mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) + .isTrue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java index b8bca3a403e1..b0d006353a98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -75,6 +76,7 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { private TestableLooper mLooper; private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; private KeyguardStateController.Callback mKeyguardStateControllerCallback; + private BiometricNotificationService mBiometricNotificationService; @Before public void setUp() { @@ -83,12 +85,10 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory(); BiometricNotificationBroadcastReceiver broadcastReceiver = new BiometricNotificationBroadcastReceiver(mContext, dialogFactory); - BiometricNotificationService biometricNotificationService = - new BiometricNotificationService(mContext, - mKeyguardUpdateMonitor, mKeyguardStateController, handler, - mNotificationManager, - broadcastReceiver); - biometricNotificationService.start(); + mBiometricNotificationService = new BiometricNotificationService(mContext, + mKeyguardUpdateMonitor, mKeyguardStateController, handler, + mNotificationManager, broadcastReceiver); + mBiometricNotificationService.start(); ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); @@ -149,4 +149,23 @@ public class BiometricNotificationServiceTest extends SysuiTestCase { .isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG); } + @Test + public void testResetFaceUnlockReEnroll_onStart() { + when(mKeyguardStateController.isShowing()).thenReturn(false); + + mKeyguardUpdateMonitorCallback.onBiometricError( + BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL, + "Testing Face Re-enrollment" /* errString */, + BiometricSourceType.FACE + ); + + mBiometricNotificationService.start(); + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS); + mLooper.processAllMessages(); + + verify(mNotificationManager, never()).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID), + mNotificationArgumentCaptor.capture(), any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 58982d13481d..7ab8e8b229a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -23,6 +23,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -61,6 +62,7 @@ import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; import android.util.Pair; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -1128,6 +1130,36 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // WHEN ACTION_HOVER is received + verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); + mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); + enterEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); + mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); + moveEvent.recycle(); + + // THEN context click haptic is played + verify(mVibrator).performHapticFeedback( + any(), + eq(HapticFeedbackConstants.CONTEXT_CLICK) + ); + } + + @Test public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); @@ -1160,6 +1192,35 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // WHEN ACTION_DOWN is received + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricExecutor.runAllReady(); + moveEvent.recycle(); + + // THEN NO haptic played + verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); + } + + @Test public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() throws RemoteException { // Disable new touch detection. @@ -1514,4 +1575,45 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN is fingerDown should be FALSE assertFalse(mUdfpsController.isFingerDown()); } + + @Test + public void playHaptic_onAodInterrupt_oneWayHapticsDisabled_onAcquiredBad_usesVibrate() + throws RemoteException { + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + + // THEN vibrate is used + verify(mVibrator).vibrate( + anyInt(), + anyString(), + eq(UdfpsController.EFFECT_CLICK), + eq("aod-lock-icon-longpress"), + eq(UdfpsController.LOCK_ICON_VIBRATION_ATTRIBUTES) + ); + } + + @Test + public void playHaptic_onAodInterrupt_oneWayHapticsEnabled_onAcquiredBad_performHapticFeedback() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + + // THEN vibrate is used + verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java index 57d3a01a4f47..cbbbe5203b24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java @@ -28,7 +28,9 @@ import android.view.View; import androidx.test.filters.SmallTest; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent; import com.android.systemui.condition.SelfExecutingMonitor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.shared.condition.Monitor; @@ -36,11 +38,10 @@ import com.android.systemui.shared.condition.Monitor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import javax.inject.Provider; - @SmallTest @RunWith(AndroidTestingRunner.class) public class DreamClockTimeComplicationTest extends SysuiTestCase { @@ -55,8 +56,10 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { private DreamClockTimeComplication mComplication; @Mock - private Provider<DreamClockTimeComplication.DreamClockTimeViewHolder> - mDreamClockTimeViewHolderProvider; + private DreamClockTimeComplicationComponent.Factory mComponentFactory; + + @Mock + private DreamClockTimeComplicationComponent mComponent; @Mock private DreamClockTimeComplication.DreamClockTimeViewHolder @@ -71,12 +74,19 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { @Mock private ComplicationLayoutParams mLayoutParams; + @Mock + private DreamClockTimeComplication.DreamClockTimeViewController mViewController; + + @Mock + private UiEventLogger mUiEventLogger; + private Monitor mMonitor; @Before public void setup() { MockitoAnnotations.initMocks(this); - when(mDreamClockTimeViewHolderProvider.get()).thenReturn(mDreamClockTimeViewHolder); + when(mComponentFactory.create()).thenReturn(mComponent); + when(mComponent.getViewHolder()).thenReturn(mDreamClockTimeViewHolder); mMonitor = SelfExecutingMonitor.createInstance(); } @@ -100,21 +110,21 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { @Test public void testComplicationRequiredTypeAvailability() { final DreamClockTimeComplication complication = - new DreamClockTimeComplication(mDreamClockTimeViewHolderProvider); + new DreamClockTimeComplication(mComponentFactory); assertEquals(Complication.COMPLICATION_TYPE_TIME, complication.getRequiredTypeAvailability()); } /** * Verifies {@link DreamClockTimeComplication.DreamClockTimeViewHolder} is obtainable from its - * provider when the complication creates view. + * component when the complication creates view. */ @Test - public void testComplicationViewHolderProviderOnCreateView() { + public void testComplicationViewHolderComponentOnCreateView() { final DreamClockTimeComplication complication = - new DreamClockTimeComplication(mDreamClockTimeViewHolderProvider); + new DreamClockTimeComplication(mComponentFactory); final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel); - verify(mDreamClockTimeViewHolderProvider).get(); + verify(mComponent).getViewHolder(); assertThat(viewHolder).isEqualTo(mDreamClockTimeViewHolder); } @@ -125,8 +135,23 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { @Test public void testComplicationViewHolderContentAccessors() { final DreamClockTimeComplication.DreamClockTimeViewHolder viewHolder = - new DreamClockTimeComplication.DreamClockTimeViewHolder(mView, mLayoutParams); + new DreamClockTimeComplication.DreamClockTimeViewHolder(mView, mLayoutParams, + mViewController); assertThat(viewHolder.getView()).isEqualTo(mView); assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams); } + + @Test + public void testClick_logUiEvent() { + final DreamClockTimeComplication.DreamClockTimeViewController controller = + new DreamClockTimeComplication.DreamClockTimeViewController(mView, mUiEventLogger); + controller.onViewAttached(); + + final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + clickListenerCaptor.getValue().onClick(mView); + verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index 63b0b2562a41..220718027eee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -234,9 +234,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { verify(mHomeControlsView).setOnClickListener(clickListenerCaptor.capture()); clickListenerCaptor.getValue().onClick(mHomeControlsView); - verify(mUiEventLogger).log( - DreamHomeControlsComplication.DreamHomeControlsChipViewController - .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED); + verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED); } private void setHaveFavorites(boolean value) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt index 71d2ec152e5a..bd3d09dfbb20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt @@ -16,8 +16,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -46,7 +44,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { } private val uiExecutor = FakeExecutor(FakeSystemClock()) - private val featureFlags = FakeFeatureFlags() @Mock lateinit var controller: ControlsControllerImpl @@ -65,7 +62,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { ActivityTestRule( /* activityFactory= */ SingleActivityFactory { TestableControlsEditingActivity( - featureFlags, uiExecutor, controller, userTracker, @@ -81,8 +77,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false) } @Test @@ -101,16 +95,7 @@ class ControlsEditingActivityTest : SysuiTestCase() { } @Test - fun testNewFlowDisabled_addControlsButton_gone() { - with(launchActivity()) { - val addControlsButton = requireViewById<Button>(R.id.addControls) - assertThat(addControlsButton.visibility).isEqualTo(View.GONE) - } - } - - @Test - fun testNewFlowEnabled_addControlsButton_visible() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) + fun testAddControlsButton_visible() { with(launchActivity()) { val addControlsButton = requireViewById<Button>(R.id.addControls) assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE) @@ -120,7 +105,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Test fun testNotLaunchFromFavoriting_saveButton_disabled() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) with(launchActivity(isFromFavoriting = false)) { val saveButton = requireViewById<Button>(R.id.done) assertThat(saveButton.isEnabled).isFalse() @@ -129,7 +113,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Test fun testLaunchFromFavoriting_saveButton_enabled() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) with(launchActivity(isFromFavoriting = true)) { val saveButton = requireViewById<Button>(R.id.done) assertThat(saveButton.isEnabled).isTrue() @@ -138,7 +121,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Test fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) with(launchActivity(isFromFavoriting = false)) { val addControls = requireViewById<Button>(R.id.addControls) @@ -177,7 +159,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { ) class TestableControlsEditingActivity( - featureFlags: FakeFeatureFlags, executor: FakeExecutor, controller: ControlsControllerImpl, userTracker: UserTracker, @@ -186,7 +167,6 @@ class ControlsEditingActivityTest : SysuiTestCase() { private val latch: CountDownLatch ) : ControlsEditingActivity( - featureFlags, executor, controller, userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt index f11c296ad572..70d93a10b445 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt @@ -17,14 +17,11 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.createLoadDataObject import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture @@ -73,8 +70,6 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Mock lateinit var controller: ControlsControllerImpl - @Mock lateinit var listingController: ControlsListingController - @Mock lateinit var userTracker: UserTracker private var latch: CountDownLatch = CountDownLatch(1) @@ -82,9 +77,6 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback> @Captor - private lateinit var listingCallback: - ArgumentCaptor<ControlsListingController.ControlsListingCallback> - @Captor private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>> @Rule @@ -93,10 +85,8 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { ActivityTestRule( /* activityFactory= */ SingleActivityFactory { TestableControlsFavoritingActivity( - featureFlags, executor, controller, - listingController, userTracker, mockDispatcher, latch @@ -109,7 +99,6 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false) } // b/259549854 to root-cause and fix @@ -130,14 +119,8 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { } @Test - fun testNewFlowEnabled_buttons() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) + fun testButtons() { with(launchActivity()) { - verify(listingController).addCallback(listingCallback.capture()) - listingCallback.value.onServicesUpdated( - listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java)) - ) - val rearrangeButton = requireViewById<Button>(R.id.rearrange) assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE) assertThat(rearrangeButton.isEnabled).isFalse() @@ -149,36 +132,8 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { } @Test - fun testNewFlowDisabled_buttons() { - with(launchActivity()) { - verify(listingController).addCallback(listingCallback.capture()) - activityRule.runOnUiThread { - listingCallback.value.onServicesUpdated( - listOf( - mock(ControlsServiceInfo::class.java), - mock(ControlsServiceInfo::class.java) - ) - ) - } - - val rearrangeButton = requireViewById<Button>(R.id.rearrange) - assertThat(rearrangeButton.visibility).isEqualTo(View.GONE) - assertThat(rearrangeButton.isEnabled).isFalse() - - val otherAppsButton = requireViewById<Button>(R.id.other_apps) - otherAppsButton.waitForPost() - assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE) - } - } - - @Test - fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() { - featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true) + fun testRearrangePressed_savesAndlaunchesActivity() { with(launchActivity()) { - verify(listingController).addCallback(capture(listingCallback)) - listingCallback.value.onServicesUpdated( - listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java)) - ) verify(controller).loadForComponent(any(), capture(controlsCallback), any()) activityRule.runOnUiThread { controlsCallback.value.accept( @@ -224,19 +179,15 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { ) class TestableControlsFavoritingActivity( - featureFlags: FeatureFlags, executor: Executor, controller: ControlsControllerImpl, - listingController: ControlsListingController, userTracker: UserTracker, private val mockDispatcher: OnBackInvokedDispatcher, private val latch: CountDownLatch ) : ControlsFavoritingActivity( - featureFlags, executor, controller, - listingController, userTracker, ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt index 272f5895390c..7ac1953ee495 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt @@ -22,8 +22,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.util.FakeSharedPreferences @@ -42,8 +40,6 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker - private val featureFlags = FakeFeatureFlags() - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -52,7 +48,6 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() { arrayOf<String>() ) whenever(userTracker.userId).thenReturn(0) - featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true) } @Test @@ -132,25 +127,8 @@ class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() { assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty() } - @Test - fun testSetAuthorizedPackageAfterFeatureDisabled() { - mContext.orCreateTestableResources.addOverride( - R.array.config_controlsPreferredPackages, - arrayOf(TEST_PACKAGE) - ) - val sharedPrefs = FakeSharedPreferences() - val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs)) - val repository = createRepository(fileManager) - - repository.removeAuthorizedPanels(setOf(TEST_PACKAGE)) - - featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false) - - assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE)) - } - private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl { - return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags) + return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker) } private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt index 0c7b9cb82b94..6230ea7ecd31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt @@ -22,7 +22,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl @@ -103,36 +102,18 @@ class SelectedComponentRepositoryTest : SysuiTestCase() { } @Test - fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() { - featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true) - - assertThat(repository.shouldAddDefaultComponent()).isTrue() - } - - @Test - fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() { - featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false) - + fun testShouldAddDefaultPanelDefaultsToTrue() { assertThat(repository.shouldAddDefaultComponent()).isTrue() } @Test - fun testFeatureEnabled_shouldAddDefaultPanelChecked() { - featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true) + fun testShouldAddDefaultPanelChecked() { repository.setShouldAddDefaultComponent(false) assertThat(repository.shouldAddDefaultComponent()).isFalse() } @Test - fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() { - featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false) - repository.setShouldAddDefaultComponent(false) - - assertThat(repository.shouldAddDefaultComponent()).isTrue() - } - - @Test fun testGetPreferredStructure_differentUserId() { sharedPreferences.savePanel(COMPONENT_A) whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt index a341ca365ada..8a1b094678e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt @@ -23,6 +23,7 @@ import com.android.systemui.log.LogcatEchoTracker /** * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests. */ +@JvmOverloads fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") = LogBuffer(name, 50, LogcatEchoTrackerAlways()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index daf5ce691df0..aa6bd4e85e24 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -1077,7 +1077,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { withArgCaptor<TransitionInfo> { verify(transitionRepository).startTransition(capture(), anyBoolean()) } - // THEN a transition to AlternateBouncer should occur + // THEN a transition to OCCLUDED should occur assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor") assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER) assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) @@ -1086,6 +1086,61 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + @Test + fun dozingToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to DOZING + runTransition(KeyguardState.LOCKSCREEN, KeyguardState.DOZING) + runCurrent() + + // WHEN the keyguard is occluded and device wakes up + keyguardRepository.setKeyguardOccluded(true) + keyguardRepository.setWakefulnessModel(startingToWake()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DOZING) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun aodToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to AOD + runTransition(KeyguardState.LOCKSCREEN, KeyguardState.AOD) + runCurrent() + + // WHEN the keyguard is occluded and aod ends + keyguardRepository.setKeyguardOccluded(true) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel( + from = DozeStateModel.DOZE_AOD, + to = DozeStateModel.FINISH, + ) + ) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.AOD) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + private fun startingToWake() = WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index d8c78ebdca49..904662ed9210 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -31,8 +32,6 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.mockito.whenever import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -75,9 +74,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { @Test fun bouncerAlpha() = runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.bouncerAlpha) repository.sendTransitionStep(step(0f, TransitionState.STARTED)) repository.sendTransitionStep(step(0.3f)) @@ -85,16 +82,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { assertThat(values.size).isEqualTo(3) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun bouncerAlpha_runDimissFromKeyguard() = runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.bouncerAlpha) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) @@ -104,16 +97,52 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { assertThat(values.size).isEqualTo(3) values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun lockscreenAlpha() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.lockscreenAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(1f)) - job.cancel() + assertThat(values.size).isEqualTo(2) + values.forEach { assertThat(it).isEqualTo(0f) } } @Test - fun scrimAlpha_runDimissFromKeyguard() = + fun lockscreenAlpha_runDimissFromKeyguard() = runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<ScrimAlpha>() + val values by collectValues(underTest.lockscreenAlpha) - val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this) + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(2) + values.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun lockscreenAlpha_leaveShadeOpen() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.lockscreenAlpha) + + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(2) + values.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun scrimAlpha_runDimissFromKeyguard() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.scrimAlpha) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) @@ -124,16 +153,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { assertThat(values.size).isEqualTo(4) values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) } - - job.cancel() } @Test fun scrimBehindAlpha_leaveShadeOpen() = runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<ScrimAlpha>() - - val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.scrimAlpha) whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true) @@ -146,16 +171,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f)) } - - job.cancel() } @Test fun scrimBehindAlpha_doNotLeaveShadeOpen() = runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<ScrimAlpha>() - - val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.scrimAlpha) whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) @@ -169,8 +190,6 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) } values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) } assertThat(values[3].behindAlpha).isEqualTo(0f) - - job.cancel() } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index c65a2d36e223..d933b57e8e15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -702,9 +702,10 @@ internal class NoteTaskControllerTest : SysuiTestCase() { // region updateNoteTaskAsUser @Test fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() { - val user = userTracker.userHandle + val user = UserHandle.CURRENT val controller = spy(createNoteTaskController()) doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) + whenever(controller.getCurrentRunningUser()).thenReturn(user) controller.updateNoteTaskAsUser(user) @@ -714,10 +715,10 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() { - // FakeUserTracker will default to UserHandle.SYSTEM. val user = UserHandle.CURRENT val controller = spy(createNoteTaskController(isEnabled = true)) doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) + whenever(controller.getCurrentRunningUser()).thenReturn(UserHandle.SYSTEM) controller.updateNoteTaskAsUser(user) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 826a6ccfbaec..56e3e9649fe7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -22,11 +22,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -91,11 +93,30 @@ class SceneContainerRepositoryTest : SysuiTestCase() { val sceneTransitionProgress by collectLastValue(underTest.transitionProgress) assertThat(sceneTransitionProgress).isEqualTo(1f) - underTest.setSceneTransitionProgress(0.1f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + underTest.setTransitionState(transitionState) + assertThat(sceneTransitionProgress).isEqualTo(1f) + + val progress = MutableStateFlow(1f) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + ) + assertThat(sceneTransitionProgress).isEqualTo(1f) + + progress.value = 0.1f assertThat(sceneTransitionProgress).isEqualTo(0.1f) - underTest.setSceneTransitionProgress(0.9f) + progress.value = 0.9f assertThat(sceneTransitionProgress).isEqualTo(0.9f) + + underTest.setTransitionState(null) + assertThat(sceneTransitionProgress).isEqualTo(1f) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 13a602dcddb0..c193d830bb20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -22,11 +22,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.SceneTransitionModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -37,7 +39,8 @@ import org.junit.runners.JUnit4 class SceneInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) - private val underTest = utils.sceneInteractor() + private val repository = utils.fakeSceneContainerRepository() + private val underTest = utils.sceneInteractor(repository = repository) @Test fun allSceneKeys() { @@ -55,11 +58,20 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun sceneTransitionProgress() = runTest { - val progress by collectLastValue(underTest.transitionProgress) - assertThat(progress).isEqualTo(1f) - - underTest.setSceneTransitionProgress(0.55f) - assertThat(progress).isEqualTo(0.55f) + val transitionProgress by collectLastValue(underTest.transitionProgress) + assertThat(transitionProgress).isEqualTo(1f) + + val progress = MutableStateFlow(0.55f) + repository.setTransitionState( + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + ), + ) + ) + assertThat(transitionProgress).isEqualTo(0.55f) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 9bcc8aa0ac12..c3540cfec72d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.systemui.shade; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.keyguard.KeyguardClockSwitch.LARGE; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; @@ -106,6 +107,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; import com.android.systemui.media.controls.ui.MediaHierarchyManager; @@ -220,7 +222,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; @Mock protected KeyguardStateController mKeyguardStateController; @Mock protected DozeLog mDozeLog; - @Mock protected ShadeLogger mShadeLog; + private final ShadeLogger mShadeLog = new ShadeLogger(logcatLogBuffer()); @Mock protected CommandQueue mCommandQueue; @Mock protected VibratorHelper mVibratorHelper; @Mock protected LatencyTracker mLatencyTracker; @@ -300,13 +302,15 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel; @Mock protected GoneToDreamingLockscreenHostedTransitionViewModel mGoneToDreamingLockscreenHostedTransitionViewModel; + @Mock protected PrimaryBouncerToGoneTransitionViewModel + mPrimaryBouncerToGoneTransitionViewModel; @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel; @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock protected MotionEvent mDownMotionEvent; @Mock protected CoroutineDispatcher mMainDispatcher; @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; - @Mock protected KeyguardLogger mKeyguardLogger; + private final KeyguardLogger mKeyguardLogger = new KeyguardLogger(logcatLogBuffer()); @Mock protected KeyguardStatusView mKeyguardStatusView; @Captor protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> @@ -502,6 +506,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(anyInt())) .thenReturn(emptyFlow()); + // Primary Bouncer->Gone + when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha()) + .thenReturn(emptyFlow()); + NotificationWakeUpCoordinator coordinator = new NotificationWakeUpCoordinator( mDumpManager, @@ -511,7 +519,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, - mock(NotificationWakeUpCoordinatorLogger.class)); + new NotificationWakeUpCoordinatorLogger(logcatLogBuffer())); mConfigurationController = new ConfigurationControllerImpl(mContext); PulseExpansionHandler expansionHandler = new PulseExpansionHandler( mContext, @@ -631,6 +639,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mGoneToDreamingTransitionViewModel, mGoneToDreamingLockscreenHostedTransitionViewModel, mLockscreenToOccludedTransitionViewModel, + mPrimaryBouncerToGoneTransitionViewModel, mMainDispatcher, mKeyguardTransitionInteractor, mDumpManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java index fe18fb53aecb..b6da20fed380 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static junit.framework.Assert.assertFalse; @@ -45,7 +46,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import org.junit.After; @@ -73,9 +73,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { // Number of notifications to use in tests requiring multiple notifications private static final int TEST_NUM_NOTIFICATIONS = 4; protected static final int TEST_TIMEOUT_TIME = 15000; - protected final Runnable TEST_TIMEOUT_RUNNABLE = () -> mTimedOut = true; - - private AlertingNotificationManager mAlertingNotificationManager; + protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true; protected NotificationEntry mEntry; protected Handler mTestHandler; @@ -84,11 +82,11 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { @Mock protected ExpandableNotificationRow mRow; - private final class TestableAlertingNotificationManager extends AlertingNotificationManager { + private static class TestableAlertingNotificationManager extends AlertingNotificationManager { private AlertEntry mLastCreatedEntry; private TestableAlertingNotificationManager(Handler handler) { - super(mock(HeadsUpManagerLogger.class), handler); + super(new HeadsUpManagerLogger(logcatLogBuffer()), handler); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME; mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; @@ -112,8 +110,8 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { } } - protected AlertingNotificationManager createAlertingNotificationManager(Handler handler) { - return new TestableAlertingNotificationManager(handler); + protected AlertingNotificationManager createAlertingNotificationManager() { + return new TestableAlertingNotificationManager(mTestHandler); } protected StatusBarNotification createNewSbn(int id, Notification n) { @@ -169,8 +167,6 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { .setSbn(mSbn) .build(); mEntry.setRow(mRow); - - mAlertingNotificationManager = createAlertingNotificationManager(mTestHandler); } @After @@ -180,68 +176,74 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { @Test public void testShowNotification_addsEntry() { - mAlertingNotificationManager.showNotification(mEntry); + AlertingNotificationManager alm = createAlertingNotificationManager(); - assertTrue(mAlertingNotificationManager.isAlerting(mEntry.getKey())); - assertTrue(mAlertingNotificationManager.hasNotifications()); - assertEquals(mEntry, mAlertingNotificationManager.getEntry(mEntry.getKey())); + alm.showNotification(mEntry); + + assertTrue(alm.isAlerting(mEntry.getKey())); + assertTrue(alm.hasNotifications()); + assertEquals(mEntry, alm.getEntry(mEntry.getKey())); } @Test public void testShowNotification_autoDismisses() { - mAlertingNotificationManager.showNotification(mEntry); - mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_TIMEOUT_TIME); + AlertingNotificationManager alm = createAlertingNotificationManager(); + + alm.showNotification(mEntry); + mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME); // Wait for remove runnable and then process it immediately TestableLooper.get(this).processMessages(1); assertFalse("Test timed out", mTimedOut); - assertFalse(mAlertingNotificationManager.isAlerting(mEntry.getKey())); + assertFalse(alm.isAlerting(mEntry.getKey())); } @Test public void testRemoveNotification_removeDeferred() { - mAlertingNotificationManager.showNotification(mEntry); + AlertingNotificationManager alm = createAlertingNotificationManager(); + alm.showNotification(mEntry); // Try to remove but defer, since the notification has not been shown long enough. - mAlertingNotificationManager.removeNotification( - mEntry.getKey(), false /* releaseImmediately */); + alm.removeNotification(mEntry.getKey(), false /* releaseImmediately */); - assertTrue(mAlertingNotificationManager.isAlerting(mEntry.getKey())); + assertTrue(alm.isAlerting(mEntry.getKey())); } @Test public void testRemoveNotification_forceRemove() { - mAlertingNotificationManager.showNotification(mEntry); + AlertingNotificationManager alm = createAlertingNotificationManager(); + alm.showNotification(mEntry); // Remove forcibly with releaseImmediately = true. - mAlertingNotificationManager.removeNotification( - mEntry.getKey(), true /* releaseImmediately */); + alm.removeNotification(mEntry.getKey(), true /* releaseImmediately */); - assertFalse(mAlertingNotificationManager.isAlerting(mEntry.getKey())); + assertFalse(alm.isAlerting(mEntry.getKey())); } @Test public void testReleaseAllImmediately() { + AlertingNotificationManager alm = createAlertingNotificationManager(); for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) { StatusBarNotification sbn = createNewNotification(i); NotificationEntry entry = new NotificationEntryBuilder() .setSbn(sbn) .build(); entry.setRow(mRow); - mAlertingNotificationManager.showNotification(entry); + alm.showNotification(entry); } - mAlertingNotificationManager.releaseAllImmediately(); + alm.releaseAllImmediately(); - assertEquals(0, mAlertingNotificationManager.getAllEntries().count()); + assertEquals(0, alm.getAllEntries().count()); } @Test public void testCanRemoveImmediately_notShownLongEnough() { - mAlertingNotificationManager.showNotification(mEntry); + AlertingNotificationManager alm = createAlertingNotificationManager(); + alm.showNotification(mEntry); // The entry has just been added so we should not remove immediately. - assertFalse(mAlertingNotificationManager.canRemoveImmediately(mEntry.getKey())); + assertFalse(alm.canRemoveImmediately(mEntry.getKey())); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index 305f48b147f8..764f7b6b8887 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -82,9 +84,9 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { mPowerInteractor, mStateController, mRemoteInputUriController, - mock(RemoteInputControllerLogger.class), + new RemoteInputControllerLogger(logcatLogBuffer()), mClickNotifier, - mock(ActionClickLogger.class), + new ActionClickLogger(logcatLogBuffer()), mock(DumpManager.class)); mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index da3a9f696d62..78c0982df414 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -22,6 +22,7 @@ import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS import com.android.systemui.statusbar.StatusBarState @@ -59,7 +60,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() { private val bypassController: KeyguardBypassController = mock() private val dozeParameters: DozeParameters = mock() private val screenOffAnimationController: ScreenOffAnimationController = mock() - private val logger: NotificationWakeUpCoordinatorLogger = mock() + private val logger = NotificationWakeUpCoordinatorLogger(logcatLogBuffer()) private val stackScrollerController: NotificationStackScrollLayoutController = mock() private val wakeUpListener: NotificationWakeUpCoordinator.WakeUpListener = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 9037df821ca8..104b751af26b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -26,6 +26,7 @@ import static android.service.notification.NotificationListenerService.REASON_CL import static android.service.notification.NotificationStats.DISMISSAL_SHADE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED; @@ -47,6 +48,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -120,7 +122,7 @@ public class NotifCollectionTest extends SysuiTestCase { @Mock private IStatusBarService mStatusBarService; @Mock private NotifPipelineFlags mNotifPipelineFlags; - @Mock private NotifCollectionLogger mLogger; + private final NotifCollectionLogger mLogger = spy(new NotifCollectionLogger(logcatLogBuffer())); @Mock private LogBufferEulogizer mEulogizer; @Mock private Handler mMainHandler; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index a8690388c3e3..bfa03eed57a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree; import static com.android.systemui.statusbar.notification.collection.ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS; @@ -101,10 +102,10 @@ import java.util.stream.Collectors; public class ShadeListBuilderTest extends SysuiTestCase { private ShadeListBuilder mListBuilder; - private FakeSystemClock mSystemClock = new FakeSystemClock(); - - @Mock private NotifPipelineFlags mNotifPipelineFlags; - @Mock private ShadeListBuilderLogger mLogger; + private final FakeSystemClock mSystemClock = new FakeSystemClock(); + private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class); + private final ShadeListBuilderLogger mLogger = new ShadeListBuilderLogger( + mNotifPipelineFlags, logcatLogBuffer()); @Mock private DumpManager mDumpManager; @Mock private NotifCollection mNotifCollection; @Mock private NotificationInteractionTracker mInteractionTracker; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java index ac9a57022f53..3dcfcfa2977b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coalescer; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.clearInvocations; @@ -62,8 +64,7 @@ public class GroupCoalescerTest extends SysuiTestCase { @Mock private NotificationListener mListenerService; @Mock private GroupCoalescer.BatchableNotificationHandler mListener; - @Mock private GroupCoalescerLogger mLogger; - + private final GroupCoalescerLogger mLogger = new GroupCoalescerLogger(logcatLogBuffer()); @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; private final NoManSimulator mNoMan = new NoManSimulator(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt index 4143647592e4..362da0b5a46c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -20,6 +20,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -28,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager import com.android.systemui.statusbar.notification.row.NotificationGuts +import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -36,6 +38,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks @SmallTest @@ -52,8 +55,9 @@ class GutsCoordinatorTest : SysuiTestCase() { @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager @Mock private lateinit var pipeline: NotifPipeline @Mock private lateinit var dumpManager: DumpManager - @Mock private lateinit var logger: GutsCoordinatorLogger + private val logger = GutsCoordinatorLogger(logcatLogBuffer()) @Mock private lateinit var lifetimeExtenderCallback: OnEndLifetimeExtensionCallback + @Mock private lateinit var notificationGuts: NotificationGuts @Before fun setUp() { @@ -69,12 +73,13 @@ class GutsCoordinatorTest : SysuiTestCase() { notifLifetimeExtender.setCallback(lifetimeExtenderCallback) entry1 = NotificationEntryBuilder().setId(1).build() entry2 = NotificationEntryBuilder().setId(2).build() + whenever(notificationGuts.gutsContent).thenReturn(mock(GutsContent::class.java)) } @Test fun testSimpleLifetimeExtension() { assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() - notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + notifGutsViewListener.onGutsOpen(entry1, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) @@ -84,9 +89,9 @@ class GutsCoordinatorTest : SysuiTestCase() { @Test fun testDoubleOpenLifetimeExtension() { assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() - notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + notifGutsViewListener.onGutsOpen(entry1, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() - notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + notifGutsViewListener.onGutsOpen(entry1, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) @@ -97,10 +102,10 @@ class GutsCoordinatorTest : SysuiTestCase() { fun testTwoEntryLifetimeExtension() { assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() - notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) + notifGutsViewListener.onGutsOpen(entry1, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() - notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java)) + notifGutsViewListener.onGutsOpen(entry2, notificationGuts) assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index ea70e9e44c66..fbd61f4fe3bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.advanceTimeBy import com.android.systemui.dump.DumpManager +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -606,7 +607,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { keyguardNotifVisibilityProvider, keyguardRepository, keyguardTransitionRepository, - mock<KeyguardCoordinatorLogger>(), + KeyguardCoordinatorLogger(logcatLogBuffer()), testScope.backgroundScope, sectionHeaderVisibilityProvider, fakeSettings, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index b5e77e0fb693..548ecdeb1f47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY; import static org.junit.Assert.assertFalse; @@ -134,7 +135,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { setSectionIsLowPriority(false); PreparationCoordinator coordinator = new PreparationCoordinator( - mock(PreparationCoordinatorLogger.class), + new PreparationCoordinatorLogger(logcatLogBuffer()), mNotifInflater, mErrorManager, mock(NotifViewBarn.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt index 5793364a1ef5..069eec22cc34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt @@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -50,7 +51,7 @@ class ShadeEventCoordinatorTest : SysuiTestCase() { private lateinit var entry2: NotificationEntry @Mock private lateinit var pipeline: NotifPipeline - @Mock private lateinit var logger: ShadeEventCoordinatorLogger + private val logger = ShadeEventCoordinatorLogger(logcatLogBuffer()) @Mock private lateinit var executor: Executor @Mock private lateinit var notifRemovedByUserCallback: Runnable @Mock private lateinit var shadeEmptiedCallback: Runnable diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt index 6eb391a631f3..0b61a8df4891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt @@ -21,14 +21,15 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @@ -36,7 +37,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @RunWith(AndroidTestingRunner::class) @RunWithLooper class NotifCollectionInconsistencyTrackerTest : SysuiTestCase() { - private val logger: NotifCollectionLogger = mock() + private val logger = spy(NotifCollectionLogger(logcatLogBuffer())) private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build() private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build() private val collectionSet = mutableSetOf<String>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index ac254abe60b4..bad56a3413a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -46,7 +47,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock() private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val viewBarn: NotifViewBarn = mock() - private val logger: NodeSpecBuilderLogger = mock() + private val logger = NodeSpecBuilderLogger(mock(), logcatLogBuffer()) private var rootController: NodeController = buildFakeController("rootController") private var headerController0: NodeController = buildFakeController("header0") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt index 6167b464c5fb..9a602728f4ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt @@ -22,7 +22,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.util.mockito.mock +import com.android.systemui.dump.logcatLogBuffer import org.junit.Assert import org.junit.Before import org.junit.Test @@ -30,6 +30,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.isNull import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.matches +import org.mockito.Mockito.spy import org.mockito.Mockito.verify @SmallTest @@ -44,7 +45,7 @@ class ShadeViewDifferTest : SysuiTestCase() { private val controller5 = FakeController(mContext, "Controller5") private val controller6 = FakeController(mContext, "Controller6") private val controller7 = FakeController(mContext, "Controller7") - private val logger: ShadeViewDifferLogger = mock() + private val logger = spy(ShadeViewDifferLogger(logcatLogBuffer())) @Before fun setUp() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index ca6598726a85..04ffab3f3a1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.notification.interruption; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -50,7 +53,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { private HeadsUpViewBinder mViewBinder; @Mock private NotificationMessagingUtil mNotificationMessagingUtil; @Mock private RowContentBindStage mBindStage; - @Mock private HeadsUpViewBinderLogger mLogger; + private final HeadsUpViewBinderLogger mLogger = spy( + new HeadsUpViewBinderLogger(logcatLogBuffer())); @Mock private NotificationEntry mEntry; @Mock private ExpandableNotificationRow mRow; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 4d4d319a3540..764005b81a5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -17,10 +17,6 @@ package com.android.systemui.statusbar.notification.row -import android.app.Notification -import android.net.Uri -import android.os.UserHandle -import android.os.UserHandle.USER_ALL import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -28,21 +24,18 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FeatureFlags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.PluginManager import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.SmartReplyController -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider import com.android.systemui.statusbar.notification.collection.render.FakeNodeController import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager import com.android.systemui.statusbar.notification.logging.NotificationLogger import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -53,9 +46,9 @@ import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.SystemClock import com.android.systemui.wmshell.BubblesManager +import java.util.Optional import junit.framework.Assert import org.junit.After import org.junit.Before @@ -63,10 +56,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import java.util.* import org.mockito.Mockito.`when` as whenever @SmallTest @@ -81,7 +72,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val activableNotificationViewController: ActivatableNotificationViewController = mock() private val rivSubComponentFactory: RemoteInputViewSubcomponent.Factory = mock() private val metricsLogger: MetricsLogger = mock() - private val logBufferLogger: NotificationRowLogger = mock() + private val logBufferLogger = NotificationRowLogger(logcatLogBuffer(), logcatLogBuffer()) private val listContainer: NotificationListContainer = mock() private val childrenContainer: NotificationChildrenContainer = mock() private val smartReplyConstants: SmartReplyConstants = mock() @@ -103,10 +94,10 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val featureFlags: FeatureFlags = mock() private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock() private val bubblesManager: BubblesManager = mock() - private val settingsController: NotificationSettingsController = mock() private val dragController: ExpandableNotificationRowDragController = mock() private val dismissibilityProvider: NotificationDismissibilityProvider = mock() private val statusBarService: IStatusBarService = mock() + private lateinit var controller: ExpandableNotificationRowController @Before @@ -119,7 +110,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { rivSubComponentFactory, metricsLogger, logBufferLogger, - mock<NotificationChildrenContainerLogger>(), + NotificationChildrenContainerLogger(logcatLogBuffer()), listContainer, smartReplyConstants, smartReplyController, @@ -143,16 +134,11 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { featureFlags, peopleNotificationIdentifier, Optional.of(bubblesManager), - settingsController, dragController, dismissibilityProvider, statusBarService ) whenever(view.childrenContainer).thenReturn(childrenContainer) - - val notification = Notification.Builder(mContext).build() - val sbn = SbnBuilder().setNotification(notification).build() - whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build()) } @After @@ -165,13 +151,13 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { whenever(view.isParentDismissed).thenReturn(true) Assert.assertTrue(controller.offerToKeepInParentForAnimation()) - Mockito.verify(view).setKeepInParentForDismissAnimation(true) + verify(view).setKeepInParentForDismissAnimation(true) } @Test fun offerKeepInParent_parentNotDismissed() { Assert.assertFalse(controller.offerToKeepInParentForAnimation()) - Mockito.verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean()) + verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean()) } @Test @@ -181,7 +167,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { whenever(view.keepInParentForDismissAnimation()).thenReturn(true) Assert.assertTrue(controller.removeFromParentIfKeptForAnimation()) - Mockito.verify(parentView).removeChildNotification(view) + verify(parentView).removeChildNotification(view) } @Test @@ -202,9 +188,9 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { controller.removeChild(childNodeController, /* isTransfer= */ true) // VERIFY the listContainer is not notified - Mockito.verify(childView).isChangingPosition = eq(true) - Mockito.verify(view).removeChildNotification(eq(childView)) - Mockito.verify(listContainer, never()).notifyGroupChildRemoved(any(), any()) + verify(childView).isChangingPosition = eq(true) + verify(view).removeChildNotification(eq(childView)) + verify(listContainer, never()).notifyGroupChildRemoved(any(), any()) } @Test @@ -216,78 +202,8 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { controller.removeChild(childNodeController, /* isTransfer= */ false) // VERIFY the listContainer is passed the childrenContainer for transient animations - Mockito.verify(childView, never()).isChangingPosition = any() - Mockito.verify(view).removeChildNotification(eq(childView)) - Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer)) - } - - @Test - fun registerSettingsListener_forBubbles() { - controller.init(mock(NotificationEntry::class.java)) - val viewStateObserver = withArgCaptor { - verify(view).addOnAttachStateChangeListener(capture()); - } - viewStateObserver.onViewAttachedToWindow(view); - verify(settingsController).addCallback(any(), any()); - } - - @Test - fun unregisterSettingsListener_forBubbles() { - controller.init(mock(NotificationEntry::class.java)) - val viewStateObserver = withArgCaptor { - verify(view).addOnAttachStateChangeListener(capture()); - } - viewStateObserver.onViewDetachedFromWindow(view); - verify(settingsController).removeCallback(any(), any()); - } - - @Test - fun settingsListener_invalidUri() { - controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1") - - verify(view, never()).getPrivateLayout() - } - - @Test - fun settingsListener_invalidUserId() { - controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1") - controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null) - - verify(view, never()).getPrivateLayout() - } - - @Test - fun settingsListener_validUserId() { - val childView: NotificationContentView = mock() - whenever(view.privateLayout).thenReturn(childView) - - controller.mSettingsListener.onSettingChanged( - BUBBLES_SETTING_URI, view.entry.sbn.userId, "1") - verify(childView).setBubblesEnabledForUser(true) - - controller.mSettingsListener.onSettingChanged( - BUBBLES_SETTING_URI, view.entry.sbn.userId, "9") - verify(childView).setBubblesEnabledForUser(false) - } - - @Test - fun settingsListener_userAll() { - val childView: NotificationContentView = mock() - whenever(view.privateLayout).thenReturn(childView) - - val notification = Notification.Builder(mContext).build() - val sbn = SbnBuilder().setNotification(notification) - .setUser(UserHandle.of(USER_ALL)) - .build() - whenever(view.entry).thenReturn(NotificationEntryBuilder() - .setSbn(sbn) - .setUser(UserHandle.of(USER_ALL)) - .build()) - - controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1") - verify(childView).setBubblesEnabledForUser(true) - - controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0") - verify(childView).setBubblesEnabledForUser(false) + verify(childView, never()).isChangingPosition = any() + verify(view).removeChildNotification(eq(childView)) + verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index bdd82fd98e0a..cf5b3cda6a18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -61,7 +63,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { mBindPipeline = new NotifBindPipeline( collection, - mock(NotifBindPipelineLogger.class), + new NotifBindPipelineLogger(logcatLogBuffer()), TestableLooper.get(this).getLooper()); mBindPipeline.setStage(mStage); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index ba6c7fd50bc5..0b90ebec3ec6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -250,9 +250,6 @@ class NotificationContentViewTest : SysuiTestCase() { .thenReturn(actionListMarginTarget) view.setContainingNotification(mockContainingNotification) - // Given: controller says bubbles are enabled for the user - view.setBubblesEnabledForUser(true); - // When: call NotificationContentView.setExpandedChild() to set the expandedChild view.expandedChild = mockExpandedChild @@ -304,9 +301,6 @@ class NotificationContentViewTest : SysuiTestCase() { view.expandedChild = mockExpandedChild assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget)) - // Given: controller says bubbles are enabled for the user - view.setBubblesEnabledForUser(true); - // When: call NotificationContentView.onNotificationUpdated() to update the // NotificationEntry, which should show bubble button view.onNotificationUpdated(createMockNotificationEntry(true)) @@ -411,6 +405,7 @@ class NotificationContentViewTest : SysuiTestCase() { val userMock: UserHandle = mock() whenever(this.sbn).thenReturn(sbnMock) whenever(sbnMock.user).thenReturn(userMock) + doReturn(showButton).whenever(view).shouldShowBubbleButton(this) } private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt deleted file mode 100644 index 2bccdcafbb6e..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.statusbar.notification.row - -import android.app.ActivityManager -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.provider.Settings.Secure -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.notification.row.NotificationSettingsController.Listener -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.SecureSettings -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class NotificationSettingsControllerTest : SysuiTestCase() { - - val setting1: String = Secure.NOTIFICATION_BUBBLES - val setting2: String = Secure.ACCESSIBILITY_ENABLED - val settingUri1: Uri = Secure.getUriFor(setting1) - val settingUri2: Uri = Secure.getUriFor(setting2) - - @Mock - private lateinit var userTracker: UserTracker - private lateinit var handler: Handler - private lateinit var testableLooper: TestableLooper - @Mock - private lateinit var secureSettings: SecureSettings - @Mock - private lateinit var dumpManager: DumpManager - - @Captor - private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback> - @Captor - private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> - - private lateinit var controller: NotificationSettingsController - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - testableLooper = TestableLooper.get(this) - handler = Handler(testableLooper.looper) - allowTestableLooperAsMainThread() - controller = - NotificationSettingsController( - userTracker, - handler, - secureSettings, - dumpManager - ) - } - - @After - fun tearDown() { - disallowTestableLooperAsMainThread() - } - - @Test - fun creationRegistersCallbacks() { - verify(userTracker).addCallback(any(), any()) - verify(dumpManager).registerNormalDumpable(anyString(), eq(controller)) - } - @Test - fun updateContentObserverRegistration_onUserChange_noSettingsListeners() { - verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) - val userCallback = userTrackerCallbackCaptor.value - val userId = 9 - - // When: User is changed - userCallback.onUserChanged(userId, context) - - // Validate: Nothing to do, since we aren't monitoring settings - verify(secureSettings, never()).unregisterContentObserver(any()) - verify(secureSettings, never()).registerContentObserverForUser( - any(Uri::class.java), anyBoolean(), any(), anyInt()) - } - @Test - fun updateContentObserverRegistration_onUserChange_withSettingsListeners() { - // When: someone is listening to a setting - controller.addCallback(settingUri1, - Mockito.mock(Listener::class.java)) - - verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) - val userCallback = userTrackerCallbackCaptor.value - val userId = 9 - - // Then: User is changed - userCallback.onUserChanged(userId, context) - - // Validate: The tracker is unregistered and re-registered with the new user - verify(secureSettings).unregisterContentObserver(any()) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri1), eq(false), any(), eq(userId)) - } - - @Test - fun addCallback_onlyFirstForUriRegistersObserver() { - controller.addCallback(settingUri1, - Mockito.mock(Listener::class.java)) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) - - controller.addCallback(settingUri1, - Mockito.mock(Listener::class.java)) - verify(secureSettings).registerContentObserverForUser( - any(Uri::class.java), anyBoolean(), any(), anyInt()) - } - - @Test - fun addCallback_secondUriRegistersObserver() { - controller.addCallback(settingUri1, - Mockito.mock(Listener::class.java)) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) - - controller.addCallback(settingUri2, - Mockito.mock(Listener::class.java)) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser())) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri1), anyBoolean(), any(), anyInt()) - } - - @Test - fun removeCallback_lastUnregistersObserver() { - val listenerSetting1 : Listener = mock() - val listenerSetting2 : Listener = mock() - controller.addCallback(settingUri1, listenerSetting1) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser())) - - controller.addCallback(settingUri2, listenerSetting2) - verify(secureSettings).registerContentObserverForUser( - eq(settingUri2), anyBoolean(), any(), anyInt()) - - controller.removeCallback(settingUri2, listenerSetting2) - verify(secureSettings, never()).unregisterContentObserver(any()) - - controller.removeCallback(settingUri1, listenerSetting1) - verify(secureSettings).unregisterContentObserver(any()) - } - - @Test - fun addCallback_updatesCurrentValue() { - whenever(secureSettings.getStringForUser( - setting1, ActivityManager.getCurrentUser())).thenReturn("9") - whenever(secureSettings.getStringForUser( - setting2, ActivityManager.getCurrentUser())).thenReturn("5") - - val listenerSetting1a : Listener = mock() - val listenerSetting1b : Listener = mock() - val listenerSetting2 : Listener = mock() - - controller.addCallback(settingUri1, listenerSetting1a) - controller.addCallback(settingUri1, listenerSetting1b) - controller.addCallback(settingUri2, listenerSetting2) - - testableLooper.processAllMessages() - - verify(listenerSetting1a).onSettingChanged( - settingUri1, ActivityManager.getCurrentUser(), "9") - verify(listenerSetting1b).onSettingChanged( - settingUri1, ActivityManager.getCurrentUser(), "9") - verify(listenerSetting2).onSettingChanged( - settingUri2, ActivityManager.getCurrentUser(), "5") - } - - @Test - fun removeCallback_noMoreUpdates() { - whenever(secureSettings.getStringForUser( - setting1, ActivityManager.getCurrentUser())).thenReturn("9") - - val listenerSetting1a : Listener = mock() - val listenerSetting1b : Listener = mock() - - // First, register - controller.addCallback(settingUri1, listenerSetting1a) - controller.addCallback(settingUri1, listenerSetting1b) - testableLooper.processAllMessages() - - verify(secureSettings).registerContentObserverForUser( - any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt()) - verify(listenerSetting1a).onSettingChanged( - settingUri1, ActivityManager.getCurrentUser(), "9") - verify(listenerSetting1b).onSettingChanged( - settingUri1, ActivityManager.getCurrentUser(), "9") - Mockito.clearInvocations(listenerSetting1b) - Mockito.clearInvocations(listenerSetting1a) - - // Remove one of them - controller.removeCallback(settingUri1, listenerSetting1a) - - // On update, only remaining listener should get the callback - settingsObserverCaptor.value.onChange(false, settingUri1) - testableLooper.processAllMessages() - - verify(listenerSetting1a, never()).onSettingChanged( - settingUri1, ActivityManager.getCurrentUser(), "9") - verify(listenerSetting1b).onSettingChanged( - settingUri1, ActivityManager.getCurrentUser(), "9") - } - -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index d21029d33d5e..1ab2b388de83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -21,6 +21,7 @@ import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static org.junit.Assert.assertEquals; @@ -178,13 +179,13 @@ public class NotificationTestHelper { contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class), - mock(RowContentBindStageLogger.class)); + new RowContentBindStageLogger(logcatLogBuffer())); CommonNotifCollection collection = mock(CommonNotifCollection.class); mBindPipeline = new NotifBindPipeline( collection, - mock(NotifBindPipelineLogger.class), + new NotifBindPipelineLogger(logcatLogBuffer()), mTestLooper.getLooper()); mBindPipeline.setStage(mBindStage); @@ -596,7 +597,7 @@ public class NotificationTestHelper { mock(NotificationGutsManager.class), mDismissibilityProvider, mock(MetricsLogger.class), - mock(NotificationChildrenContainerLogger.class), + new NotificationChildrenContainerLogger(logcatLogBuffer()), mock(SmartReplyConstants.class), mock(SmartReplyController.class), mFeatureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 7c99568ee75f..32f0fe7d136a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; @@ -68,7 +69,7 @@ public class RowContentBindStageTest extends SysuiTestCase { mRowContentBindStage = new RowContentBindStage( mBinder, mock(NotifInflationErrorManager.class), - mock(RowContentBindStageLogger.class)); + new RowContentBindStageLogger(logcatLogBuffer())); mRowContentBindStage.createStageParams(mEntry); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 07eadf7c9bb4..6f431be29d73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; @@ -154,8 +155,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; @Mock private InteractionJankMonitor mJankMonitor; - @Mock private StackStateLogger mStackLogger; - @Mock private NotificationStackScrollLogger mLogger; + private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(), + logcatLogBuffer()); + private final NotificationStackScrollLogger mLogger = new NotificationStackScrollLogger( + logcatLogBuffer(), logcatLogBuffer(), logcatLogBuffer()); @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator; @Mock private NotificationTargetsHelper mNotificationTargetsHelper; @Mock private SecureSettings mSecureSettings; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index 6fda56c8717a..72522caf21b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -30,6 +32,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.AlertingNotificationManager; @@ -61,7 +64,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { private HeadsUpManagerPhone mHeadsUpManager; - @Mock private HeadsUpManagerLogger mHeadsUpManagerLogger; + private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger( + logcatLogBuffer()); @Mock private GroupMembershipManager mGroupManager; @Mock private VisualStabilityProvider mVSProvider; @Mock private StatusBarStateController mStatusBarStateController; @@ -104,11 +108,13 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { } } + @Override protected AlertingNotificationManager createAlertingNotificationManager() { return mHeadsUpManager; } @Before + @Override public void setUp() { AccessibilityManagerWrapper accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); @@ -116,8 +122,10 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { .thenReturn(TEST_AUTO_DISMISS_TIME); when(mVSProvider.isReorderingAllowed()).thenReturn(true); mDependency.injectMockDependency(NotificationShadeWindowController.class); - super.setUp(); + mContext.getOrCreateTestableResources().addOverride( + R.integer.ambient_notification_extension_time, 500); + super.setUp(); mHeadsUpManager = new TestableHeadsUpManagerPhone( mContext, mHeadsUpManagerLogger, @@ -134,8 +142,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { } @After + @Override public void tearDown() { - mTestHandler.removeCallbacksAndMessages(null); + super.tearDown(); } @Test @@ -181,7 +190,6 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey())); } - @Test public void testExtendHeadsUp() { mHeadsUpManager.showNotification(mEntry); @@ -189,7 +197,7 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey()); mTestHandler.postDelayed(pastNormalTimeRunnable, TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2); - mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_TIMEOUT_TIME); + mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME); mHeadsUpManager.extendHeadsUp(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 33144f233a71..08a76ef01e77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -20,6 +20,7 @@ import static android.service.notification.NotificationListenerService.REASON_CL import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; @@ -42,6 +43,7 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.os.Handler; @@ -248,7 +250,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(StatusBarRemoteInputCallback.class), mActivityIntentHelper, mock(MetricsLogger.class), - mock(StatusBarNotificationActivityStarterLogger.class), + new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()), mOnUserInteractionCallback, mock(NotificationPresenter.class), mock(ShadeViewController.class), @@ -410,10 +412,12 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Test public void testOnFullScreenIntentWhenDozing_wakeUpDevice() { // GIVEN entry that can has a full screen intent that can show + PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 1, + new Intent("fake_full_screen"), PendingIntent.FLAG_IMMUTABLE); Notification.Builder nb = new Notification.Builder(mContext, "a") .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon) - .setFullScreenIntent(mock(PendingIntent.class), true); + .setFullScreenIntent(fullScreenIntent, true); StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag" + System.currentTimeMillis(), 0, 0, nb.build(), new UserHandle(0), null, 0); @@ -437,6 +441,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // GIVEN entry that can has a full screen intent that can show PendingIntent mockFullScreenIntent = mock(PendingIntent.class); when(mockFullScreenIntent.getCreatorUid()).thenReturn(kTestUid); + when(mockFullScreenIntent.getIntent()).thenReturn(new Intent("fake_full_screen")); ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = new ActivityInfo(); resolveInfo.activityInfo.name = kTestActivityName; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java index a797e032a353..14edf3dba6c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -26,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -70,7 +73,7 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { @Mock private NotificationEntry mEntry; @Mock private StatusBarNotification mSbn; @Mock private Notification mNotification; - @Mock private HeadsUpManagerLogger mLogger; + private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); @Mock private AccessibilityManagerWrapper mAccessibilityMgr; private final class TestableHeadsUpManager extends HeadsUpManager { @@ -86,14 +89,17 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { } } + @Override protected AlertingNotificationManager createAlertingNotificationManager() { return mHeadsUpManager; } @Before + @Override public void setUp() { initMocks(this); when(mEntry.getSbn()).thenReturn(mSbn); + when(mEntry.getKey()).thenReturn("entryKey"); when(mSbn.getNotification()).thenReturn(mNotification); super.setUp(); mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler, @@ -101,8 +107,9 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { } @After + @Override public void tearDown() { - mTestHandler.removeCallbacksAndMessages(null); + super.tearDown(); } @Test @@ -169,7 +176,7 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey()); mTestHandler.postDelayed(pastNormalTimeRunnable, (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2); - mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_A11Y_TIMEOUT_TIME); + mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_A11Y_TIMEOUT_TIME); TestableLooper.get(this).processMessages(2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index c06dbdcf3a1b..7f3d4b7f9f76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -14,6 +14,7 @@ package com.android.systemui.statusbar.policy; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -24,10 +25,10 @@ import static org.mockito.Mockito.when; import android.app.NotificationManager; import android.os.Handler; -import android.os.Looper; import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; @@ -45,6 +46,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -61,9 +65,10 @@ public class ZenModeControllerImplTest extends SysuiTestCase { DumpManager mDumpManager; @Mock UserTracker mUserTracker; - private ZenModeControllerImpl mController; + private final FakeSettings mGlobalSettings = new FakeSettings(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -72,10 +77,10 @@ public class ZenModeControllerImplTest extends SysuiTestCase { mController = new ZenModeControllerImpl( mContext, - Handler.createAsync(Looper.myLooper()), + Handler.createAsync(TestableLooper.get(this).getLooper()), mBroadcastDispatcher, mDumpManager, - new FakeSettings(), + mGlobalSettings, mUserTracker); } @@ -131,4 +136,48 @@ public class ZenModeControllerImplTest extends SysuiTestCase { mController.addCallback(null); mController.fireConfigChanged(null); } + + @Test + public void testModeChange() { + List<Integer> states = List.of( + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, + Settings.Global.ZEN_MODE_ALARMS, + Settings.Global.ZEN_MODE_ALARMS + ); + + for (Integer state : states) { + mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state); + TestableLooper.get(this).processAllMessages(); + assertEquals(state.intValue(), mController.getZen()); + } + } + + @Test + public void testModeChange_callbackNotified() { + final AtomicInteger currentState = new AtomicInteger(-1); + + ZenModeController.Callback callback = new Callback() { + @Override + public void onZenChanged(int zen) { + currentState.set(zen); + } + }; + + mController.addCallback(callback); + + List<Integer> states = List.of( + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, + Settings.Global.ZEN_MODE_ALARMS, + Settings.Global.ZEN_MODE_ALARMS + ); + + for (Integer state : states) { + mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state); + TestableLooper.get(this).processAllMessages(); + assertEquals(state.intValue(), currentState.get()); + } + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index f299ad45a7ea..7593e8429e60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -18,6 +18,8 @@ package com.android.systemui.toast; import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; +import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -63,7 +65,6 @@ import com.android.internal.util.IntPair; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.PluginManager; import com.android.systemui.statusbar.CommandQueue; @@ -110,8 +111,7 @@ public class ToastUITest extends SysuiTestCase { @Mock private IAccessibilityManager mAccessibilityManager; @Mock private PluginManager mPluginManager; @Mock private DumpManager mDumpManager; - @Mock private ToastLogger mToastLogger; - @Mock private FeatureFlags mFeatureFlags; + private final ToastLogger mToastLogger = spy(new ToastLogger(logcatLogBuffer())); @Mock private PackageManager mPackageManager; @Mock private ITransientNotificationCallback mCallback; diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c81910855f78..ee11cb63a375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -16,14 +16,20 @@ package com.android.systemui.volume; +import static android.media.AudioManager.RINGER_MODE_NORMAL; +import static android.media.AudioManager.RINGER_MODE_SILENT; +import static android.media.AudioManager.RINGER_MODE_VIBRATE; + import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertTrue; +import static org.junit.Assume.assumeNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -360,7 +366,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { public void testSelectVibrateFromDrawer_OnewayAPI_On() { mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); mActiveRinger.performClick(); @@ -390,7 +396,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { public void testSelectMuteFromDrawer_OnewayAPI_On() { mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); mActiveRinger.performClick(); @@ -428,7 +434,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { // Make sure we've actually changed the ringer mode. verify(mVolumeDialogController, times(1)).setRingerMode( - AudioManager.RINGER_MODE_NORMAL, false); + RINGER_MODE_NORMAL, false); } /** @@ -625,6 +631,88 @@ public class VolumeDialogImplTest extends SysuiTestCase { } } + private enum RingerDrawerState {INIT, OPEN, CLOSE} + + @Test + public void ringerModeNormal_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.INIT); + } + + @Test + public void ringerModeSilent_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.INIT); + } + + @Test + public void ringerModeVibrate_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.INIT); + } + + @Test + public void ringerModeNormal_openDrawer_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.OPEN); + } + + @Test + public void ringerModeSilent_openDrawer_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.OPEN); + } + + @Test + public void ringerModeVibrate_openDrawer_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.OPEN); + } + + @Test + public void ringerModeNormal_closeDrawer_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.CLOSE); + } + + @Test + public void ringerModeSilent_closeDrawer_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.CLOSE); + } + + @Test + public void ringerModeVibrate_closeDrawer_ringerContainerDescribesItsState() { + assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE); + } + + /** + * The content description should include ringer state, and the correct one. + */ + private void assertRingerContainerDescribesItsState(int ringerMode, + RingerDrawerState drawerState) { + State state = createShellState(); + state.ringerModeInternal = ringerMode; + mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); + mDialog.onStateChangedH(state); + + mDialog.show(SHOW_REASON_UNKNOWN); + + if (drawerState != RingerDrawerState.INIT) { + // in both cases we first open the drawer + mDialog.toggleRingerDrawer(true); + + if (drawerState == RingerDrawerState.CLOSE) { + mDialog.toggleRingerDrawer(false); + } + } + + String ringerContainerDescription = mDialog.getSelectedRingerContainerDescription(); + assumeNotNull(ringerContainerDescription); + + String ringerDescription = mContext.getString( + mDialog.getStringDescriptionResourceForRingerMode(ringerMode)); + + if (drawerState == RingerDrawerState.OPEN) { + assertEquals(ringerDescription, ringerContainerDescription); + } else { + assertNotSame(ringerDescription, ringerContainerDescription); + assertTrue(ringerContainerDescription.startsWith(ringerDescription)); + } + } + @After public void teardown() { cleanUp(mDialog); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java index 8e4f184f560e..53e5e7d859ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java @@ -188,6 +188,25 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { } @Test + public void queryWalletCards_walletEnabled_queryMultipleCards() { + mController.queryWalletCards(mCardsRetriever, 5); + + verify(mQuickAccessWalletClient) + .getWalletCards( + eq(MoreExecutors.directExecutor()), mRequestCaptor.capture(), + eq(mCardsRetriever)); + + GetWalletCardsRequest request = mRequestCaptor.getValue(); + assertEquals(5, mRequestCaptor.getValue().getMaxCards()); + assertEquals( + mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width), + request.getCardWidthPx()); + assertEquals( + mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height), + request.getCardHeightPx()); + } + + @Test public void queryWalletCards_walletFeatureNotAvailable_noQuery() { when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt index 3901d720ab3a..d5bdb59b5cda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt @@ -215,7 +215,7 @@ class WalletContextualSuggestionsControllerTest : SysuiTestCase() { cards: List<WalletCard> = emptyList(), shouldFail: Boolean = false ) { - whenever(walletController.queryWalletCards(any())).thenAnswer { invocation -> + whenever(walletController.queryWalletCards(any(), anyInt())).thenAnswer { invocation -> with( invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback ) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 26a75d0cc70a..70d15a068356 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -119,9 +119,11 @@ class SceneTestUtils( ) } - fun sceneInteractor(): SceneInteractor { + fun sceneInteractor( + repository: SceneContainerRepository = fakeSceneContainerRepository() + ): SceneInteractor { return SceneInteractor( - repository = fakeSceneContainerRepository(), + repository = repository, ) } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b76e99eb5431..37abe1b1aee3 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2404,6 +2404,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mLoggedInlineDatasetShown = true; } mService.logDatasetShown(this.id, mClientState, uiType); + Slog.d(TAG, "onShown(): " + uiType); } } diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index 5a9c4704aa7d..c2d2468bbe44 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -44,8 +44,9 @@ import java.util.Map; * Manages communication with companion applications via * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to * the services, maintaining the connection (the binding), and invoking callback methods such as - * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)} and - * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} in the application process. + * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)}, + * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and + * {@link CompanionDeviceService#onDeviceEvent(AssociationInfo, int)} in the application process. * * <p> * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be @@ -53,8 +54,7 @@ import java.util.Map; * <ul> * <li> {@link #bindCompanionApplication(int, String, boolean)} * <li> {@link #unbindCompanionApplication(int, String)} - * <li> {@link #notifyCompanionApplicationDeviceAppeared(AssociationInfo)} - * <li> {@link #notifyCompanionApplicationDeviceDisappeared(AssociationInfo)} + * <li> {@link #notifyCompanionApplicationDeviceEvent(AssociationInfo, int)} (AssociationInfo, int)} * <li> {@link #isCompanionApplicationBound(int, String)} * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} * </ul> @@ -240,19 +240,16 @@ public class CompanionApplicationController { void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); - if (DEBUG) { - Log.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId + + Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId + "/" + packageName); - } final CompanionDeviceServiceConnector primaryServiceConnector = getPrimaryServiceConnector(userId, packageName); if (primaryServiceConnector == null) { - if (DEBUG) { - Log.e(TAG, "notify_CompanionApplicationDevice_Appeared(): " + Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): " + "u" + userId + "/" + packageName + " is NOT bound."); - Log.d(TAG, "Stacktrace", new Throwable()); - } + Slog.e(TAG, "Stacktrace", new Throwable()); return; } @@ -265,19 +262,16 @@ public class CompanionApplicationController { void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); - if (DEBUG) { - Log.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId - + "/" + packageName); - } + + Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId + + "/" + packageName); final CompanionDeviceServiceConnector primaryServiceConnector = getPrimaryServiceConnector(userId, packageName); if (primaryServiceConnector == null) { - if (DEBUG) { - Log.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): " + Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): " + "u" + userId + "/" + packageName + " is NOT bound."); - Log.d(TAG, "Stacktrace", new Throwable()); - } + Slog.e(TAG, "Stacktrace", new Throwable()); return; } @@ -287,6 +281,27 @@ public class CompanionApplicationController { primaryServiceConnector.postOnDeviceDisappeared(association); } + void notifyCompanionApplicationDeviceEvent(AssociationInfo association, int event) { + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + final CompanionDeviceServiceConnector primaryServiceConnector = + getPrimaryServiceConnector(userId, packageName); + + if (primaryServiceConnector == null) { + Slog.e(TAG, "notifyCompanionApplicationDeviceEvent(): " + + "u" + userId + "/" + packageName + + " event=[ " + event + " ] is NOT bound."); + Slog.e(TAG, "Stacktrace", new Throwable()); + return; + } + + Slog.i(TAG, "Calling onDeviceEvent() to userId=[" + userId + "] package=[" + + packageName + "] associationId=[" + association.getId() + + "] state=[" + event + "]"); + + primaryServiceConnector.postOnDeviceEvent(association, event); + } + void dump(@NonNull PrintWriter out) { out.append("Companion Device Application Controller: \n"); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 82d4d60b21d8..c5501f1d3e0d 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -24,6 +24,12 @@ import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESE import static android.Manifest.permission.USE_COMPANION_TRANSPORTS; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; @@ -383,17 +389,8 @@ public class CompanionDeviceManagerService extends SystemService { if (!association.shouldBindWhenPresent()) return; - final int userId = association.getUserId(); - final String packageName = association.getPackageName(); - // Set bindImportant to true when the association is self-managed to avoid the target - // service being killed. - final boolean bindImportant = association.isSelfManaged(); + bindApplicationIfNeeded(association); - if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { - mCompanionAppController.bindCompanionApplication(userId, packageName, bindImportant); - } else if (DEBUG) { - Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); - } mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association); } @@ -414,11 +411,57 @@ public class CompanionDeviceManagerService extends SystemService { if (association.shouldBindWhenPresent()) { mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association); } + } - // Check if there are other devices associated to the app that are present. - if (shouldBindPackage(userId, packageName)) return; + private void onDeviceEventInternal(int associationId, int event) { + Slog.i(TAG, "onDeviceEventInternal() id=" + associationId + " event= " + event); + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + final String packageName = association.getPackageName(); + final int userId = association.getUserId(); + switch (event) { + case DEVICE_EVENT_BLE_APPEARED: + case DEVICE_EVENT_BT_CONNECTED: + case DEVICE_EVENT_SELF_MANAGED_APPEARED: + if (!association.shouldBindWhenPresent()) return; + + bindApplicationIfNeeded(association); + + mCompanionAppController.notifyCompanionApplicationDeviceEvent( + association, event); + break; + case DEVICE_EVENT_BLE_DISAPPEARED: + case DEVICE_EVENT_BT_DISCONNECTED: + case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED: + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); + return; + } + if (association.shouldBindWhenPresent()) { + mCompanionAppController.notifyCompanionApplicationDeviceEvent( + association, event); + } + // Check if there are other devices associated to the app that are present. + if (shouldBindPackage(userId, packageName)) return; + mCompanionAppController.unbindCompanionApplication(userId, packageName); + break; + default: + Slog.e(TAG, "Event: " + event + "is not supported"); + break; + } + } - mCompanionAppController.unbindCompanionApplication(userId, packageName); + private void bindApplicationIfNeeded(AssociationInfo association) { + final String packageName = association.getPackageName(); + final int userId = association.getUserId(); + // Set bindImportant to true when the association is self-managed to avoid the target + // service being killed. + final boolean bindImportant = association.isSelfManaged(); + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + mCompanionAppController.bindCompanionApplication( + userId, packageName, bindImportant); + } else if (DEBUG) { + Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); + } } /** @@ -883,7 +926,6 @@ public class CompanionDeviceManagerService extends SystemService { + " active=" + active + " deviceAddress=" + deviceAddress); } - final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, packageName); @@ -912,10 +954,17 @@ public class CompanionDeviceManagerService extends SystemService { // an application sets/unsets the mNotifyOnDeviceNearby flag. mAssociationStore.updateAssociation(association); + int associationId = association.getId(); // If device is already present, then trigger callback. - if (active && mDevicePresenceMonitor.isDevicePresent(association.getId())) { - if (DEBUG) Log.d(TAG, "Device is already present. Triggering callback."); - onDeviceAppearedInternal(association.getId()); + if (active && mDevicePresenceMonitor.isDevicePresent(associationId)) { + Slog.i(TAG, "Device is already present. Triggering callback."); + if (mDevicePresenceMonitor.isBlePresent(associationId) + || mDevicePresenceMonitor.isSimulatePresent(associationId)) { + onDeviceAppearedInternal(associationId); + onDeviceEventInternal(associationId, DEVICE_EVENT_BLE_APPEARED); + } else if (mDevicePresenceMonitor.isBtConnected(associationId)) { + onDeviceEventInternal(associationId, DEVICE_EVENT_BT_CONNECTED); + } } // If last listener is unregistered, then unbind application. @@ -1380,6 +1429,11 @@ public class CompanionDeviceManagerService extends SystemService { public void onDeviceDisappeared(int associationId) { onDeviceDisappearedInternal(associationId); } + + @Override + public void onDeviceEvent(int associationId, int event) { + onDeviceEventInternal(associationId, event); + } }; private final PackageMonitor mPackageMonitor = new PackageMonitor() { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index 82628a6c0b72..928842c79190 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -106,6 +106,11 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) { post(companionService -> companionService.onDeviceDisappeared(associationInfo)); } + void postOnDeviceEvent(@NonNull AssociationInfo associationInfo, int event) { + post(companionService -> companionService.onDeviceEvent(associationInfo, event)); + } + + /** * Post "unbind" job, which will run *after* all previously posted jobs complete. diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 94cede80aa3b..d368b8626e2e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -107,15 +107,10 @@ class CompanionDeviceShellCommand extends ShellCommand { mService.loadAssociationsFromDisk(); break; - case "simulate-device-appeared": + case "simulate-device-event": associationId = getNextIntArgRequired(); - mDevicePresenceMonitor.simulateDeviceAppeared(associationId); - break; - - case "simulate-device-disappeared": - associationId = getNextIntArgRequired(); - mDevicePresenceMonitor.simulateDeviceDisappeared(associationId); - break; + int event = getNextIntArgRequired(); + mDevicePresenceMonitor.simulateDeviceEvent(associationId, event); case "remove-inactive-associations": { // This command should trigger the same "clean-up" job as performed by the @@ -320,7 +315,9 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); - pw.println(" simulate-device-appeared ASSOCIATION_ID"); + pw.println(" simulate-device-event ASSOCIATION_ID EVENT"); + pw.println(" Simulate the companion device event changes:"); + pw.println(" Case(0): "); pw.println(" Make CDM act as if the given companion device has appeared."); pw.println(" I.e. bind the associated companion application's"); pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback."); @@ -328,15 +325,17 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'"); pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out" + "."); - pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); - - pw.println(" simulate-device-disappeared ASSOCIATION_ID"); + pw.println(" Case(1): "); pw.println(" Make CDM act as if the given companion device has disappeared."); pw.println(" I.e. unbind the associated companion application's"); pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback."); pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was"); pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than"); pw.println(" 60 seconds ago."); + pw.println(" Case(2): "); + pw.println(" Make CDM act as if the given companion device is BT connected "); + pw.println(" Case(3): "); + pw.println(" Make CDM act as if the given companion device is BT disconnected "); pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); pw.println(" remove-inactive-associations"); diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java index 0b2cce0666c5..7e3079073c90 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -56,7 +56,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.util.Log; import android.util.Slog; @@ -73,20 +72,6 @@ import java.util.Set; class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { private static final String TAG = "CDM_BleCompanionDeviceScanner"; - /** - * When using {@link ScanSettings#SCAN_MODE_LOW_POWER}, it usually takes from 20 seconds to - * 2 minutes for the BLE scanner to find advertisements sent from the same device. - * On the other hand, {@link android.bluetooth.BluetoothAdapter.LeScanCallback} will report - * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST} 10 sec after it finds the - * advertisement for the first time (add reports - * {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH FIRST_MATCH}). - * To avoid constantly reporting {@link Callback#onBleCompanionDeviceFound(int) onDeviceFound()} - * and {@link Callback#onBleCompanionDeviceLost(int) onDeviceLost()} (while the device is - * actually present) to its clients, {@link BleCompanionDeviceScanner}, will add 1-minute delay - * after it receives {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST}. - */ - private static final int NOTIFY_DEVICE_LOST_DELAY = 2 * 60 * 1000; // 2 Min. - interface Callback { void onBleCompanionDeviceFound(int associationId); @@ -95,7 +80,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { private final @NonNull AssociationStore mAssociationStore; private final @NonNull Callback mCallback; - private final @NonNull MainThreadHandler mMainThreadHandler; // Non-null after init(). private @Nullable BluetoothAdapter mBtAdapter; @@ -108,7 +92,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { @NonNull AssociationStore associationStore, @NonNull Callback callback) { mAssociationStore = associationStore; mCallback = callback; - mMainThreadHandler = new MainThreadHandler(); } @MainThread @@ -146,7 +129,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { if (Looper.getMainLooper().isCurrentThread()) { restartScan(); } else { - mMainThreadHandler.post(this::restartScan); + new Handler(Looper.getMainLooper()).post(this::restartScan); } } @@ -182,11 +165,10 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { } @MainThread - private void startScan() { + void startScan() { enforceInitialized(); if (DEBUG) Log.i(TAG, "startScan()"); - // This method should not be called if scan is already in progress. if (mScanning) { Slog.w(TAG, "Scan is already in progress."); @@ -243,7 +225,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { } } - private void stopScanIfNeeded() { + void stopScanIfNeeded() { enforceInitialized(); if (DEBUG) Log.i(TAG, "stopScan()"); @@ -343,16 +325,11 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { switch (callbackType) { case CALLBACK_TYPE_FIRST_MATCH: - if (mMainThreadHandler.hasNotifyDeviceLostMessages(device)) { - mMainThreadHandler.removeNotifyDeviceLostMessages(device); - return; - } - notifyDeviceFound(device); break; case CALLBACK_TYPE_MATCH_LOST: - mMainThreadHandler.sendNotifyDeviceLostDelayedMessage(device); + notifyDeviceLost(device); break; default: @@ -370,36 +347,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { } }; - @SuppressLint("HandlerLeak") - private class MainThreadHandler extends Handler { - private static final int NOTIFY_DEVICE_LOST = 1; - - MainThreadHandler() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(@NonNull Message message) { - if (message.what != NOTIFY_DEVICE_LOST) return; - - final BluetoothDevice device = (BluetoothDevice) message.obj; - notifyDeviceLost(device); - } - - void sendNotifyDeviceLostDelayedMessage(BluetoothDevice device) { - final Message message = obtainMessage(NOTIFY_DEVICE_LOST, device); - sendMessageDelayed(message, NOTIFY_DEVICE_LOST_DELAY); - } - - boolean hasNotifyDeviceLostMessages(BluetoothDevice device) { - return hasEqualMessages(NOTIFY_DEVICE_LOST, device); - } - - void removeNotifyDeviceLostMessages(BluetoothDevice device) { - removeEqualMessages(NOTIFY_DEVICE_LOST, device); - } - } - private static String nameForBtState(int state) { return nameForState(state) + "(" + state + ")"; } diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index a5410e448c7b..6ba85bdda9e4 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -169,6 +169,7 @@ public class BluetoothCompanionDeviceConnectionListener } for (AssociationInfo association : associations) { + if (!association.isNotifyOnDeviceNearby()) continue; final int id = association.getId(); if (connected) { mCallback.onBluetoothCompanionDeviceConnected(id); diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index f6e9415a2a7e..f45a1c42e14f 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -16,6 +16,12 @@ package com.android.server.companion.presence; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED; +import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; @@ -32,6 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.UserManager; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.server.companion.AssociationStore; @@ -54,6 +61,7 @@ import java.util.Set; * <li> {@link #isDevicePresent(int)} * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)} * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)} + * <li> {@link Callback#onDeviceStateChanged(int, int)}} * </ul> */ @SuppressLint("LongLogTag") @@ -69,6 +77,9 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange /** Invoked when a companion device no longer seen nearby or disconnects. */ void onDeviceDisappeared(int associationId); + + /**Invoked when device has corresponding event changes. */ + void onDeviceEvent(int associationId, int state); } private final @NonNull AssociationStore mAssociationStore; @@ -127,6 +138,28 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange } /** + * @return whether the current device is BT connected and had already reported to the app. + */ + + public boolean isBtConnected(int associationId) { + return mConnectedBtDevices.contains(associationId); + } + + /** + * @return whether the current device in BLE range and had already reported to the app. + */ + public boolean isBlePresent(int associationId) { + return mNearbyBleDevices.contains(associationId); + } + + /** + * @return whether the current device had been already reported by the simulator. + */ + public boolean isSimulatePresent(int associationId) { + return mSimulated.contains(associationId); + } + + /** * Marks a "self-managed" device as connected. * * <p> @@ -136,7 +169,8 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()} */ public void onSelfManagedDeviceConnected(int associationId) { - onDevicePresent(mReportedSelfManagedDevices, associationId, "application-reported"); + onDeviceEvent(mReportedSelfManagedDevices, + associationId, DEVICE_EVENT_SELF_MANAGED_APPEARED); } /** @@ -149,45 +183,53 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()} */ public void onSelfManagedDeviceDisconnected(int associationId) { - onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported"); + onDeviceEvent(mReportedSelfManagedDevices, + associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED); } /** * Marks a "self-managed" device as disconnected when binderDied. */ public void onSelfManagedDeviceReporterBinderDied(int associationId) { - onDeviceGone(mReportedSelfManagedDevices, associationId, "application-reported"); + onDeviceEvent(mReportedSelfManagedDevices, + associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED); } @Override public void onBluetoothCompanionDeviceConnected(int associationId) { - onDevicePresent(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt"); + Slog.i(TAG, "onBluetoothCompanionDeviceConnected: " + + "associationId( " + associationId + " )"); + onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_CONNECTED); + // Stop scanning for BLE devices when this device is connected + // and there are no other devices to connect to. + if (canStopBleScan()) { + mBleScanner.stopScanIfNeeded(); + } } @Override public void onBluetoothCompanionDeviceDisconnected(int associationId) { - // If disconnected device is also a BLE device, skip the 2-minute timer and mark it as gone. - boolean isConnectableBleDevice = mNearbyBleDevices.remove(associationId); - if (DEBUG && isConnectableBleDevice) { - Log.d(TAG, "Bluetooth device disconnect was detected." - + " Pre-emptively marking the BLE device as lost."); - } - onDeviceGone(mConnectedBtDevices, associationId, /* sourceLoggingTag */ "bt"); + Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected " + + "associationId( " + associationId + " )"); + // Start BLE scanning when the device is disconnected. + mBleScanner.startScan(); + + onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_DISCONNECTED); } @Override public void onBleCompanionDeviceFound(int associationId) { - onDevicePresent(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble"); + onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_APPEARED); } @Override public void onBleCompanionDeviceLost(int associationId) { - onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble"); + onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_DISAPPEARED); } /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ @TestApi - public void simulateDeviceAppeared(int associationId) { + public void simulateDeviceEvent(int associationId, int state) { // IMPORTANT: this API should only be invoked via the // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to // make this call are SHELL and ROOT. @@ -196,25 +238,32 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange // Make sure the association exists. enforceAssociationExists(associationId); - onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated"); + switch (state) { + case DEVICE_EVENT_BLE_APPEARED: + simulateDeviceAppeared(associationId, state); + break; + case DEVICE_EVENT_BT_CONNECTED: + onBluetoothCompanionDeviceConnected(associationId); + break; + case DEVICE_EVENT_BLE_DISAPPEARED: + simulateDeviceDisappeared(associationId, state); + break; + case DEVICE_EVENT_BT_DISCONNECTED: + onBluetoothCompanionDeviceDisconnected(associationId); + break; + default: + throw new IllegalArgumentException("State: " + state + "is not supported"); + } + } + private void simulateDeviceAppeared(int associationId, int state) { + onDeviceEvent(mSimulated, associationId, state); mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); } - /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ - @TestApi - public void simulateDeviceDisappeared(int associationId) { - // IMPORTANT: this API should only be invoked via the - // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to - // make this call are SHELL and ROOT. - // No other caller (including SYSTEM!) should be allowed. - enforceCallerShellOrRoot(); - // Make sure the association exists. - enforceAssociationExists(associationId); - + private void simulateDeviceDisappeared(int associationId, int state) { mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); - - onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated"); + onDeviceEvent(mSimulated, associationId, state); } private void enforceAssociationExists(int associationId) { @@ -224,58 +273,59 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange } } - private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource, - int newDeviceAssociationId, @NonNull String sourceLoggingTag) { - if (DEBUG) { - Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId - + ", source=" + sourceLoggingTag); - Log.d(TAG, " > association=" - + mAssociationStore.getAssociationById(newDeviceAssociationId)); - } - - final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId); - if (alreadyPresent) { - Log.i(TAG, "Device" + "id (" + newDeviceAssociationId + ") already present."); - } - - final boolean added = presentDevicesForSource.add(newDeviceAssociationId); - if (!added) { - Log.w(TAG, "Association with id " - + newDeviceAssociationId + " is ALREADY reported as " - + "present by this source (" + sourceLoggingTag + ")"); - } - - if (alreadyPresent) return; - - mCallback.onDeviceAppeared(newDeviceAssociationId); - } - - private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource, - int goneDeviceAssociationId, @NonNull String sourceLoggingTag) { - if (DEBUG) { - Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId - + ", source=" + sourceLoggingTag); - Log.d(TAG, " > association=" - + mAssociationStore.getAssociationById(goneDeviceAssociationId)); + private void onDeviceEvent(@NonNull Set<Integer> presentDevicesForSource, + int associationId, int event) { + Slog.i(TAG, "onDeviceEvent() id=" + associationId + ", state=" + event); + + switch (event) { + case DEVICE_EVENT_BLE_APPEARED: + case DEVICE_EVENT_BT_CONNECTED: + case DEVICE_EVENT_SELF_MANAGED_APPEARED: + final boolean alreadyPresent = isDevicePresent(associationId); + final boolean added = presentDevicesForSource.add(associationId); + if (!added) { + Slog.w(TAG, "Association with id " + + associationId + " is ALREADY reported as " + + "present by this source, event=" + event); + return; + } + // For backward compatibility, do not send the onDeviceAppeared() callback + // if it already reported BLE device status. + if (event == DEVICE_EVENT_BT_CONNECTED && alreadyPresent) { + Slog.i(TAG, "Ignore sending onDeviceAppeared callback, " + + "device id (" + associationId + ") already present."); + } else { + mCallback.onDeviceAppeared(associationId); + } + + break; + case DEVICE_EVENT_BLE_DISAPPEARED: + case DEVICE_EVENT_BT_DISCONNECTED: + case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED: + final boolean removed = presentDevicesForSource.remove(associationId); + if (!removed) { + Log.w(TAG, "Association with id " + associationId + " was NOT reported " + + "as present by this source, event= " + event); + + return; + } + final boolean stillPresent = isDevicePresent(associationId); + // For backward compatibility, do not send the onDeviceDisappeared() + // callback when ble scan still presenting. + if (stillPresent) { + Slog.i(TAG, "Ignore sending onDeviceDisappeared callback, " + + "device id (" + associationId + ") is still present."); + } else { + mCallback.onDeviceDisappeared(associationId); + } + + break; + default: + Slog.e(TAG, "Event: " + event + " is not supported"); + return; } - final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId); - if (!removed) { - Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported " - + "as present by this source (" + sourceLoggingTag + ")"); - - return; - } - - final boolean stillPresent = isDevicePresent(goneDeviceAssociationId); - if (stillPresent) { - if (DEBUG) { - Log.i(TAG, " Device id (" + goneDeviceAssociationId + ") is still present."); - } - return; - } - - mCallback.onDeviceDisappeared(goneDeviceAssociationId); + mCallback.onDeviceEvent(associationId, event); } /** @@ -316,6 +366,19 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange throw new SecurityException("Caller is neither Shell nor Root"); } + private boolean canStopBleScan() { + for (AssociationInfo ai : mAssociationStore.getAssociations()) { + int id = ai.getId(); + // The BLE scan cannot be stopped if there's a device is not yet connected. + if (ai.isNotifyOnDeviceNearby() && !isBtConnected(id)) { + Slog.i(TAG, "The BLE scan cannot be stopped, " + + "device( " + id + " ) is not yet connected"); + return false; + } + } + return true; + } + /** * Dumps system information about devices that are marked as "present". */ @@ -386,7 +449,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange public void handleMessage(@NonNull Message msg) { final int associationId = msg.what; if (mSimulated.contains(associationId)) { - onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated"); + onDeviceEvent(mSimulated, associationId, DEVICE_EVENT_BLE_DISAPPEARED); } } } diff --git a/services/core/Android.bp b/services/core/Android.bp index 459e24d5b42e..9f263c842965 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -187,6 +187,7 @@ java_library_static { "ImmutabilityAnnotation", "securebox", "apache-commons-math", + "power_optimization_flags_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index f19f7f212513..1741593ba671 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -19,12 +19,10 @@ package com.android.server; import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.Sensor; @@ -40,8 +38,6 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.os.VibrationEffect; -import android.os.Vibrator; import android.provider.Settings; import android.util.MutableBoolean; import android.util.Slog; @@ -113,19 +109,6 @@ public class GestureLauncherService extends SystemService { */ private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2; - /** Action for starting emergency alerts on Wear OS. */ - private static final String WEAR_LAUNCH_EMERGENCY_ACTION = - "com.android.systemui.action.LAUNCH_EMERGENCY"; - - /** Action for starting emergency alerts in retail mode on Wear OS. */ - private static final String WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION = - "com.android.systemui.action.LAUNCH_EMERGENCY_RETAIL"; - - /** - * Boolean extra for distinguishing intents coming from power button gesture. - */ - private static final String EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE = "launch_emergency_via_gesture"; - /** The listener that receives the gesture event. */ private final GestureEventListener mGestureListener = new GestureEventListener(); private final CameraLiftTriggerEventListener mCameraLiftTriggerListener = @@ -198,7 +181,6 @@ public class GestureLauncherService extends SystemService { private final UiEventLogger mUiEventLogger; private boolean mHasFeatureWatch; - private long mVibrateMilliSecondsForPanicGesture; @VisibleForTesting public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum { @@ -268,13 +250,6 @@ public class GestureLauncherService extends SystemService { mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); - mVibrateMilliSecondsForPanicGesture = - resources.getInteger( - com.android - .internal - .R - .integer - .config_mashPressVibrateTimeOnPowerButton); } } @@ -714,11 +689,6 @@ public class GestureLauncherService extends SystemService { userSetupComplete)); } - if (mHasFeatureWatch) { - onEmergencyGestureDetectedOnWatch(); - return true; - } - StatusBarManagerInternal service = LocalServices.getService( StatusBarManagerInternal.class); service.onEmergencyActionLaunchGestureDetected(); @@ -728,37 +698,6 @@ public class GestureLauncherService extends SystemService { } } - private void onEmergencyGestureDetectedOnWatch() { - Intent emergencyIntent = - new Intent( - isInRetailMode() - ? WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION - : WEAR_LAUNCH_EMERGENCY_ACTION); - PackageManager pm = mContext.getPackageManager(); - ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0); - if (resolveInfo == null) { - Slog.w(TAG, "Couldn't find an app to process the emergency intent " - + emergencyIntent.getAction()); - return; - } - - Vibrator vibrator = mContext.getSystemService(Vibrator.class); - vibrator.vibrate(VibrationEffect.createOneShot(mVibrateMilliSecondsForPanicGesture, - VibrationEffect.DEFAULT_AMPLITUDE)); - - emergencyIntent.setComponent( - new ComponentName( - resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)); - emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - emergencyIntent.putExtra(EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE, true); - mContext.startActivityAsUser(emergencyIntent, new UserHandle(mUserId)); - } - - private boolean isInRetailMode() { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DEVICE_DEMO_MODE, 0) == 1; - } - private boolean isUserSetupComplete() { return Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index dc83125503a9..da2588b12054 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2414,7 +2414,7 @@ public final class ActiveServices { // Even if the service is already a FGS, we need to update the notification, // so we need to call it again. signalForegroundServiceObserversLocked(r); - r.postNotification(); + r.postNotification(true); if (r.app != null) { updateServiceForegroundLocked(psr, true); } @@ -2472,7 +2472,7 @@ public final class ActiveServices { } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { // if it's been deferred, force to visibility if (!r.mFgsNotificationShown) { - r.postNotification(); + r.postNotification(false); } dropFgsNotificationStateLocked(r); if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) { @@ -2916,7 +2916,7 @@ public final class ActiveServices { // in the interval, so we lazy check whether we still need to show // the notification. if (r.isForeground && r.app != null) { - r.postNotification(); + r.postNotification(true); r.mFgsNotificationShown = true; } else { if (DEBUG_FOREGROUND_SERVICE) { @@ -5340,7 +5340,7 @@ public final class ActiveServices { thread.scheduleCreateService(r, r.serviceInfo, null /* compatInfo (unused but need to keep method signature) */, app.mState.getReportedProcState()); - r.postNotification(); + r.postNotification(false); created = true; } catch (DeadObjectException e) { Slog.w(TAG, "Application dead when creating service " + r); @@ -6434,6 +6434,7 @@ public final class ActiveServices { } updateServiceConnectionActivitiesLocked(psr); psr.removeAllConnections(); + psr.removeAllSdkSandboxConnections(); psr.mAllowlistManager = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index bef53c7f31ee..faf1900a6609 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -22,6 +22,7 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean; import android.annotation.NonNull; import android.app.ActivityThread; @@ -153,6 +154,11 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time"; static final String KEY_USE_MODERN_TRIM = "use_modern_trim"; + /** + * Whether or not to enable the new oom adjuster implementation. + */ + static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj"; + private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; @@ -216,6 +222,11 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_USE_MODERN_TRIM = true; /** + * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}. + */ + private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false; + + /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} */ private static final int @@ -1051,6 +1062,9 @@ final class ActivityManagerConstants extends ContentObserver { /** @see #KEY_USE_MODERN_TRIM */ public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM; + /** @see #KEY_ENABLE_NEW_OOMADJ */ + public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ; + private final OnPropertiesChangedListener mOnDeviceConfigChangedListener = new OnPropertiesChangedListener() { @Override @@ -1308,6 +1322,7 @@ final class ActivityManagerConstants extends ContentObserver { CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES) - rawMaxEmptyProcesses) / 3; + loadNativeBootDeviceConfigConstants(); } public void start(ContentResolver resolver) { @@ -1347,6 +1362,11 @@ final class ActivityManagerConstants extends ContentObserver { DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS)); } + private void loadNativeBootDeviceConfigConstants() { + ENABLE_NEW_OOMADJ = getDeviceConfigBoolean(KEY_ENABLE_NEW_OOMADJ, + DEFAULT_ENABLE_NEW_OOM_ADJ); + } + public void setOverrideMaxCachedProcesses(int value) { mOverrideMaxCachedProcesses = value; updateMaxCachedProcesses(); @@ -1997,6 +2017,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_USE_MODERN_TRIM); } + private void updateEnableNewOomAdj() { + ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_ENABLE_NEW_OOMADJ, + DEFAULT_ENABLE_NEW_OOM_ADJ); + } + private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) { ForegroundServiceTypePolicy.getDefaultPolicy() .updatePermissionEnforcementFlagIfNecessary(name); @@ -2187,6 +2214,9 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME); pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME); + pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ); + pw.print("="); pw.println(ENABLE_NEW_OOMADJ); + pw.println(); if (mOverrideMaxCachedProcesses >= 0) { pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a0fae26fcac1..c1f2f6731e43 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2029,6 +2029,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM); addPidLocked(app); + mOomAdjuster.onProcessBeginLocked(app); updateLruProcessLocked(app, false, null); updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); } @@ -2422,7 +2423,9 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.init(this, activeUids, mPlatformCompat); mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null); mPhantomProcessList = new PhantomProcessList(this); - mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread); + mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread) + : new OomAdjuster(this, mProcessList, activeUids, handlerThread); mIntentFirewall = null; mProcessStats = new ProcessStatsService(this, mContext.getCacheDir()); @@ -2483,7 +2486,9 @@ public class ActivityManagerService extends IActivityManager.Stub mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), new LowMemDetector(this)); mPhantomProcessList = new PhantomProcessList(this); - mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); + mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(this, mProcessList, activeUids) + : new OomAdjuster(this, mProcessList, activeUids); // Broadcast policy parameters final BroadcastConstants foreConstants = new BroadcastConstants( @@ -4595,6 +4600,7 @@ public class ActivityManagerService extends IActivityManager.Stub EventLogTags.writeAmProcBound(app.userId, pid, app.processName); synchronized (mProcLock) { + mOomAdjuster.onProcessBeginLocked(app); mOomAdjuster.setAttachingProcessStatesLSP(app); clearProcessForegroundLocked(app); app.setDebugging(false); @@ -6980,6 +6986,7 @@ public class ActivityManagerService extends IActivityManager.Stub sdkSandboxClientAppPackage, new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION, customProcess != null ? customProcess : info.processName)); + mOomAdjuster.onProcessBeginLocked(app); updateLruProcessLocked(app, false, null); updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 9026c20fe529..b7fc4844413f 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -109,6 +109,7 @@ import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; +import com.android.server.power.optimization.Flags; import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; @@ -2605,6 +2606,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub awaitCompletion(); synchronized (mStats) { mStats.dumpConstantsLocked(pw); + + pw.println("Flags:"); + pw.println(" " + Flags.FLAG_STREAMLINED_BATTERY_STATS + + ": " + Flags.streamlinedBatteryStats()); } } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 8c1fd516028e..2fff79b3fb26 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -373,7 +373,7 @@ public class BroadcastConstants { * Return the {@link SystemProperty} name for the given key in our * {@link DeviceConfig} namespace. */ - private @NonNull String propertyFor(@NonNull String key) { + private static @NonNull String propertyFor(@NonNull String key) { return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key; } @@ -382,11 +382,11 @@ public class BroadcastConstants { * {@link DeviceConfig} namespace, but with a different prefix that can be * used to locally override the {@link DeviceConfig} value. */ - private @NonNull String propertyOverrideFor(@NonNull String key) { + private static @NonNull String propertyOverrideFor(@NonNull String key) { return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key; } - private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) { + static boolean getDeviceConfigBoolean(@NonNull String key, boolean def) { return SystemProperties.getBoolean(propertyOverrideFor(key), SystemProperties.getBoolean(propertyFor(key), def)); } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index cc9628fd0a72..1a58556cfe46 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -238,7 +238,7 @@ public final class CachedAppOptimizer { private static final String ATRACE_COMPACTION_TRACK = "Compaction"; private static final String ATRACE_FREEZER_TRACK = "Freezer"; - private static final int FREEZE_BINDER_TIMEOUT_MS = 100; + private static final int FREEZE_BINDER_TIMEOUT_MS = 0; private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000; // If enabled, any compaction issued will apply to code mappings and share memory mappings. diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 459c6ff3504a..1f9e89e30782 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -41,6 +41,7 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; @@ -124,6 +125,7 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal.OomAdjReason; @@ -369,20 +371,21 @@ public class OomAdjuster { */ private final Handler mProcessGroupHandler; - private final int[] mTmpSchedGroup = new int[1]; + protected final int[] mTmpSchedGroup = new int[1]; - private final ActivityManagerService mService; - private final ProcessList mProcessList; - private final ActivityManagerGlobalLock mProcLock; + final ActivityManagerService mService; + final ProcessList mProcessList; + final ActivityManagerGlobalLock mProcLock; private final int mNumSlots; - private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>(); - private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>(); - private final ActiveUids mTmpUidRecords; - private final ArrayDeque<ProcessRecord> mTmpQueue; - private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>(); - private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>(); - private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>(); + protected final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>(); + protected final ArrayList<ProcessRecord> mTmpProcessList2 = new ArrayList<ProcessRecord>(); + protected final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>(); + protected final ActiveUids mTmpUidRecords; + protected final ArrayDeque<ProcessRecord> mTmpQueue; + protected final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>(); + protected final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>(); + protected final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>(); /** * Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate @@ -412,7 +415,7 @@ public class OomAdjuster { this(service, processList, activeUids, createAdjusterThread()); } - private static ServiceThread createAdjusterThread() { + static ServiceThread createAdjusterThread() { // The process group is usually critical to the response time of foreground app, so the // setter should apply it as soon as possible. final ServiceThread adjusterThread = @@ -532,7 +535,7 @@ public class OomAdjuster { mPendingProcessSet.remove(app); mProcessesInCycle.clear(); - computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true); + computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true); if (!mProcessesInCycle.isEmpty()) { // We can't use the score here if there is a cycle, abort. for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) { @@ -550,7 +553,7 @@ public class OomAdjuster { && (uidRec.getSetProcState() != uidRec.getCurProcState() || uidRec.getSetCapability() != uidRec.getCurCapability() || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) { - ActiveUids uids = mTmpUidRecords; + final ActiveUids uids = mTmpUidRecords; uids.clear(); uids.put(uidRec.getUid(), uidRec); updateUidsLSP(uids, SystemClock.elapsedRealtime()); @@ -633,19 +636,20 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); mAdjSeq++; - // Firstly, try to see if the importance of itself gets changed final ProcessStateRecord state = app.mState; final boolean wasCached = state.isCached(); final int oldAdj = state.getCurRawAdj(); final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ ? oldAdj : UNKNOWN_ADJ; + + // Firstly, try to see if the importance of itself gets changed final boolean wasBackground = ActivityManager.isProcStateBackground( state.getSetProcState()); final int oldCap = state.getSetCapability(); @@ -693,8 +697,6 @@ public class OomAdjuster { mPendingProcessSet.clear(); if (!containsCycle) { - // Reset the flag - state.setReachable(false); // Remove this app from the return list because we've done the computation on it. processes.remove(app); } @@ -718,8 +720,13 @@ public class OomAdjuster { return true; } + /** + * Collect the reachable processes from the given {@code apps}, the result will be + * returned in the given {@code processes}, which will include the processes from + * the given {@code apps}. + */ @GuardedBy("mService") - private boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps, + protected boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps, ArrayList<ProcessRecord> processes, ActiveUids uids) { final ArrayDeque<ProcessRecord> queue = mTmpQueue; queue.clear(); @@ -824,11 +831,15 @@ public class OomAdjuster { if (size > 0) { // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it. for (int l = 0, r = size - 1; l < r; l++, r--) { - ProcessRecord t = processes.get(l); - processes.set(l, processes.get(r)); + final ProcessRecord t = processes.get(l); + final ProcessRecord u = processes.get(r); + t.mState.setReachable(false); + u.mState.setReachable(false); + processes.set(l, u); processes.set(r, t); } } + return containsCycle; } @@ -928,24 +939,18 @@ public class OomAdjuster { * Update OomAdj for all processes within the given list (could be partial), or the whole LRU * list if the given list is null; when it's partial update, each process's client proc won't * get evaluated recursively here. + * + * <p>Note: If the given {@code processes} is not null, the expectation to it is, the caller + * must have called {@link collectReachableProcessesLocked} on it. */ @GuardedBy({"mService", "mProcLock"}) - private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles, boolean startProfiling) { - if (startProfiling) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); - mService.mOomAdjProfiler.oomAdjStarted(); - } - final long now = SystemClock.uptimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final long oldTime = now - mConstants.mMaxEmptyTimeMillis; final boolean fullUpdate = processes == null; + final ArrayList<ProcessRecord> activeProcesses = fullUpdate + ? mProcessList.getLruProcessesLOSP() : processes; ActiveUids activeUids = uids; - ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP() - : processes; - final int numProc = activeProcesses.size(); - if (activeUids == null) { final int numUids = mActiveUids.size(); activeUids = mTmpUidRecords; @@ -956,14 +961,14 @@ public class OomAdjuster { } } - // Reset state in all uid records. - for (int i = activeUids.size() - 1; i >= 0; i--) { - final UidRecord uidRec = activeUids.valueAt(i); - if (DEBUG_UID_OBSERVERS) { - Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); - } - uidRec.reset(); + if (startProfiling) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); } + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - mConstants.mMaxEmptyTimeMillis; + final int numProc = activeProcesses.size(); mAdjSeq++; if (fullUpdate) { @@ -971,6 +976,9 @@ public class OomAdjuster { mNewNumAServiceProcs = 0; } + // Reset state in all uid records. + resetUidRecordsLsp(activeUids); + boolean retryCycles = false; boolean computeClients = fullUpdate || potentialCycles; @@ -996,8 +1004,9 @@ public class OomAdjuster { if (!app.isKilledByAm() && app.getThread() != null) { state.setProcStateChanged(false); app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason); + // It won't enter cycle if not computing clients. computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false, - computeClients); // It won't enter cycle if not computing clients. + computeClients, oomAdjReason, true); // if any app encountered a cycle, we need to perform an additional loop later retryCycles |= state.containsCycle(); // Keep the completedAdjSeq to up to date. @@ -1034,7 +1043,7 @@ public class OomAdjuster { final ProcessStateRecord state = app.mState; if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) { if (computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, - true, true)) { + true, true, oomAdjReason, true)) { retryCycles = true; } } @@ -1045,10 +1054,33 @@ public class OomAdjuster { assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + + if (startProfiling) { + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void resetUidRecordsLsp(@NonNull ActiveUids activeUids) { + // Reset state in all uid records. + for (int i = activeUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = activeUids.valueAt(i); + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + + @GuardedBy({"mService", "mProcLock"}) + protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids, + long now, long nowElapsed, long oldTime) { mNumNonCachedProcs = 0; mNumCachedHiddenProcs = 0; - boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, + final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids, oomAdjReason); mNumServiceProcs = mNewNumServiceProcs; @@ -1085,14 +1117,10 @@ public class OomAdjuster { Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms"); } } - if (startProfiling) { - mService.mOomAdjProfiler.oomAdjEnded(); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } } @GuardedBy({"mService", "mProcLock"}) - private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { + protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) { final int numLru = lruList.size(); if (mConstants.USE_TIERED_CACHED_ADJ) { final long now = SystemClock.uptimeMillis(); @@ -1413,7 +1441,7 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) { + protected void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) { if (!app.isKilledByAm() && app.getThread() != null) { if (app.isolated && app.mServices.numberOfRunningServices() <= 0 && app.getIsolatedEntryPoint() == null) { @@ -1442,7 +1470,7 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) { + protected void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) { // This compares previously set procstate to the current procstate in regards to whether // or not the app's network access will be blocked. So, this needs to be called before // we update the UidRecord's procstate by calling {@link UidRecord#setSetProcState}. @@ -1580,7 +1608,7 @@ public class OomAdjuster { return true; } - private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = + protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = new ComputeOomAdjWindowCallback(); /** These methods are called inline during computeOomAdjLSP(), on the same thread */ @@ -1719,24 +1747,30 @@ public class OomAdjuster { } @GuardedBy({"mService", "mProcLock"}) - private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj, + protected boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj, ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval, - boolean computeClients) { + boolean computeClients, int oomAdjReason, boolean couldRecurse) { final ProcessStateRecord state = app.mState; - if (mAdjSeq == state.getAdjSeq()) { - if (state.getAdjSeq() == state.getCompletedAdjSeq()) { - // This adjustment has already been computed successfully. - return false; - } else { - // The process is being computed, so there is a cycle. We cannot - // rely on this process's state. - state.setContainsCycle(true); - mProcessesInCycle.add(app); + if (couldRecurse) { + if (mAdjSeq == state.getAdjSeq()) { + if (state.getAdjSeq() == state.getCompletedAdjSeq()) { + // This adjustment has already been computed successfully. + return false; + } else { + // The process is being computed, so there is a cycle. We cannot + // rely on this process's state. + state.setContainsCycle(true); + mProcessesInCycle.add(app); - return false; + return false; + } } } + int prevAppAdj = getInitialAdj(app); + int prevProcState = getInitialProcState(app); + int prevCapability = getInitialCapability(app); + if (app.getThread() == null) { state.setAdjSeq(mAdjSeq); state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND); @@ -1745,6 +1779,8 @@ public class OomAdjuster { state.setCurRawAdj(CACHED_APP_MAX_ADJ); state.setCompletedAdjSeq(state.getAdjSeq()); state.setCurCapability(PROCESS_CAPABILITY_NONE); + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAppAdj); return false; } @@ -1753,7 +1789,7 @@ public class OomAdjuster { state.setAdjTarget(null); state.setEmpty(false); state.setCached(false); - if (!cycleReEval) { + if (!couldRecurse || !cycleReEval) { // Don't reset this flag when doing cycles re-evaluation. state.setNoKillOnBgRestrictedAndIdle(false); // If this UID is currently allowlisted, it should not be frozen. @@ -1764,9 +1800,6 @@ public class OomAdjuster { final int appUid = app.info.uid; final int logUid = mService.mCurOomAdjUid; - int prevAppAdj = state.getCurAdj(); - int prevProcState = state.getCurProcState(); - int prevCapability = state.getCurCapability(); final ProcessServiceRecord psr = app.mServices; if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) { @@ -1812,6 +1845,8 @@ public class OomAdjuster { state.setCurRawProcState(state.getCurProcState()); state.setCurAdj(state.getMaxAdj()); state.setCompletedAdjSeq(state.getAdjSeq()); + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAppAdj); // if curAdj is less than prevAppAdj, then this process was promoted return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState; } @@ -1825,7 +1860,7 @@ public class OomAdjuster { int adj; int schedGroup; int procState; - int capability = cycleReEval ? app.mState.getCurCapability() : 0; + int capability = cycleReEval ? getInitialCapability(app) : 0; boolean foregroundActivities = false; boolean hasVisibleActivities = false; @@ -1904,7 +1939,7 @@ public class OomAdjuster { // value that the caller wants us to. adj = cachedAdj; procState = PROCESS_STATE_CACHED_EMPTY; - if (!state.containsCycle()) { + if (!couldRecurse || !state.containsCycle()) { state.setCached(true); state.setEmpty(true); state.setAdjType("cch-empty"); @@ -2169,8 +2204,10 @@ public class OomAdjuster { } } - boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp(); - boolean scheduleLikeTopApp = false; + state.setCurBoundByNonBgRestrictedApp(getInitialIsCurBoundByNonBgRestrictedApp(app)); + + state.setScheduleLikeTopApp(false); + for (int is = psr.numberOfRunningServices() - 1; is >= 0 && (adj > FOREGROUND_APP_ADJ || schedGroup == SCHED_GROUP_BACKGROUND @@ -2243,6 +2280,18 @@ public class OomAdjuster { } } + if (!couldRecurse) { + // We're entering recursive functions below, if we're told it's not a recursive + // loop, abort here. + continue; + } + + + state.setCurRawAdj(adj); + state.setCurRawProcState(procState); + state.setCurrentSchedulingGroup(schedGroup); + state.setCurCapability(capability); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); for (int conni = serviceConnections.size() - 1; conni >= 0 && (adj > FOREGROUND_APP_ADJ @@ -2263,335 +2312,13 @@ public class OomAdjuster { continue; } - boolean trackedProcState = false; - - ProcessRecord client = cr.binding.client; - if (app.isSdkSandbox && cr.binding.attributedClient != null) { - // For SDK sandboxes, use the attributed client (eg the app that - // requested the sandbox) - client = cr.binding.attributedClient; - } - final ProcessStateRecord cstate = client.mState; - if (computeClients) { - computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, - cycleReEval, true); - } else { - cstate.setCurRawAdj(cstate.getCurAdj()); - cstate.setCurRawProcState(cstate.getCurProcState()); - } - - int clientAdj = cstate.getCurRawAdj(); - int clientProcState = cstate.getCurRawProcState(); - - final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP; - - boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted()); - - if (client.mOptRecord.shouldNotFreeze()) { - // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); - } - - // We always propagate PROCESS_CAPABILITY_BFSL over bindings here, - // but, right before actually setting it to the process, - // we check the final procstate, and remove it if the procsate is below BFGS. - capability |= getBfslCapabilityFromClient(client); - - if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { - capability |= cstate.getCurCapability(); - } - - // If an app has network capability by default - // (by having procstate <= BFGS), then the apps it binds to will get - // elevated to a high enough procstate anyway to get network unless they - // request otherwise, so don't propagate the network capability by default - // in this case unless they explicitly request it. - if ((cstate.getCurCapability() - & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) { - if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { - // This is used to grant network access to Expedited Jobs. - if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) { - capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; - } - } else { - capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; - } - } - if ((cstate.getCurCapability() - & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) { - if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) { - // This is used to grant network access to User Initiated Jobs. - if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { - capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; - } - } - } - - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } + computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll, + cycleReEval, computeClients, oomAdjReason, cachedAdj, true); - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. The specific cached state - // doesn't propagate except under certain conditions. - clientProcState = PROCESS_STATE_CACHED_EMPTY; - } - String adjType = null; - if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { - // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. - if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); - } - // Not doing bind OOM management, so treat - // this guy more like a started service. - if (state.hasShownUi() && !state.getCachedIsHomeProcess()) { - // If this process has shown some UI, let it immediately - // go to the LRU list because it may be pretty heavy with - // UI stuff. We'll tag it with a label just to help - // debug and understand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-ui-services"; - } - state.setCached(false); - clientAdj = adj; - clientProcState = procState; - } else { - if (now >= (s.lastActivity - + mConstants.MAX_SERVICE_INACTIVITY)) { - // This service has not seen activity within - // recent memory, so allow it to drop to the - // LRU list if there is no other reason to keep - // it around. We'll also tag it with a label just - // to help debug and undertand what is going on. - if (adj > clientAdj) { - adjType = "cch-bound-services"; - } - clientAdj = adj; - } - } - } - if (adj > clientAdj) { - // If this process has recently shown UI, and - // the process that is binding to it is less - // important than being visible, then we don't - // care about the binding as much as we care - // about letting this process get into the LRU - // list to be killed and restarted if needed for - // memory. - if (state.hasShownUi() && !state.getCachedIsHomeProcess() - && clientAdj > PERCEPTIBLE_APP_ADJ) { - if (adj >= CACHED_APP_MIN_ADJ) { - adjType = "cch-bound-ui-services"; - } - } else { - int newAdj; - int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj. - if (cr.hasFlag(Context.BIND_ABOVE_CLIENT - | Context.BIND_IMPORTANT)) { - if (clientAdj >= PERSISTENT_SERVICE_ADJ) { - newAdj = clientAdj; - } else { - // make this service persistent - newAdj = PERSISTENT_SERVICE_ADJ; - schedGroup = SCHED_GROUP_DEFAULT; - procState = ActivityManager.PROCESS_STATE_PERSISTENT; - cr.trackProcState(procState, mAdjSeq); - trackedProcState = true; - } - } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) - && clientAdj <= PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) { - newAdj = PERCEPTIBLE_LOW_APP_ADJ; - } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) - && cr.notHasFlag(Context.BIND_NOT_FOREGROUND) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { - // This is for user-initiated jobs. - // We use APP_ADJ + 1 here, so we can tell them apart from FGS. - newAdj = PERCEPTIBLE_APP_ADJ + 1; - } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) - && cr.hasFlag(Context.BIND_NOT_FOREGROUND) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) { - // This is for expedited jobs. - // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart - // EJ and short-FGS. - newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2; - } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE) - && clientAdj < PERCEPTIBLE_APP_ADJ - && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { - newAdj = PERCEPTIBLE_APP_ADJ; - } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) { - newAdj = clientAdj; - } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) - && clientAdj <= VISIBLE_APP_ADJ - && adj > VISIBLE_APP_ADJ) { - newAdj = VISIBLE_APP_ADJ; - } else { - if (adj > VISIBLE_APP_ADJ) { - // TODO: Is this too limiting for apps bound from TOP? - newAdj = Math.max(clientAdj, lbAdj); - } else { - newAdj = adj; - } - } - if (!cstate.isCached()) { - state.setCached(false); - } - if (adj > newAdj) { - adj = newAdj; - state.setCurRawAdj(adj); - adjType = "service"; - } - } - } - if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND - | Context.BIND_IMPORTANT_BACKGROUND)) { - // This will treat important bound services identically to - // the top app, which may behave differently than generic - // foreground work. - final int curSchedGroup = cstate.getCurrentSchedulingGroup(); - if (curSchedGroup > schedGroup) { - if (cr.hasFlag(Context.BIND_IMPORTANT)) { - schedGroup = curSchedGroup; - } else { - schedGroup = SCHED_GROUP_DEFAULT; - } - } - if (clientProcState < PROCESS_STATE_TOP) { - // Special handling for above-top states (persistent - // processes). These should not bring the current process - // into the top state, since they are not on top. Instead - // give them the best bound state after that. - if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { - clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; - } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else if (mService.mWakefulness.get() - == PowerManagerInternal.WAKEFULNESS_AWAKE - && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) - { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } else { - clientProcState = - PROCESS_STATE_IMPORTANT_FOREGROUND; - } - } else if (clientProcState == PROCESS_STATE_TOP) { - // Go at most to BOUND_TOP, unless requested to elevate - // to client's state. - clientProcState = PROCESS_STATE_BOUND_TOP; - final boolean enabled = cstate.getCachedCompatChange( - CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY); - if (enabled) { - if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { - // TOP process passes all capabilities to the service. - capability |= cstate.getCurCapability(); - } else { - // TOP process passes no capability to the service. - } - } else { - // TOP process passes all capabilities to the service. - capability |= cstate.getCurCapability(); - } - } - } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) { - if (clientProcState < - PROCESS_STATE_TRANSIENT_BACKGROUND) { - clientProcState = - PROCESS_STATE_TRANSIENT_BACKGROUND; - } - } else { - if (clientProcState < - PROCESS_STATE_IMPORTANT_BACKGROUND) { - clientProcState = - PROCESS_STATE_IMPORTANT_BACKGROUND; - } - } - - if (schedGroup < SCHED_GROUP_TOP_APP - && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) - && clientIsSystem) { - schedGroup = SCHED_GROUP_TOP_APP; - scheduleLikeTopApp = true; - } - - if (!trackedProcState) { - cr.trackProcState(clientProcState, mAdjSeq); - } - - if (procState > clientProcState) { - procState = clientProcState; - state.setCurRawProcState(procState); - if (adjType == null) { - adjType = "service"; - } - } - if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND - && cr.hasFlag(Context.BIND_SHOWING_UI)) { - app.setPendingUiClean(true); - } - if (adjType != null) { - state.setAdjType(adjType); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(client); - state.setAdjSourceProcState(clientProcState); - state.setAdjTarget(s.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } - } - } else { // BIND_WAIVE_PRIORITY == true - // BIND_WAIVE_PRIORITY bindings are special when it comes to the - // freezer. Processes bound via WPRI are expected to be running, - // but they are not promoted in the LRU list to keep them out of - // cached. As a result, they can freeze based on oom_adj alone. - // Normally, bindToDeath would fire when a cached app would die - // in the background, but nothing will fire when a running process - // pings a frozen process. Accordingly, any cached app that is - // bound by an unfrozen app via a WPRI binding has to remain - // unfrozen. - if (clientAdj < CACHED_APP_MIN_ADJ) { - app.mOptRecord.setShouldNotFreeze(true); - } - } - if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { - psr.setTreatLikeActivity(true); - } - final ActivityServiceConnectionsHolder a = cr.activity; - if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) { - if (a != null && adj > FOREGROUND_APP_ADJ - && a.isActivityVisible()) { - adj = FOREGROUND_APP_ADJ; - state.setCurRawAdj(adj); - if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { - if (cr.hasFlag(Context.BIND_IMPORTANT)) { - schedGroup = SCHED_GROUP_TOP_APP_BOUND; - } else { - schedGroup = SCHED_GROUP_DEFAULT; - } - } - state.setCached(false); - state.setAdjType("service"); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE); - state.setAdjSource(a); - state.setAdjSourceProcState(procState); - state.setAdjTarget(s.instanceName); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise to service w/activity: " + app); - } - } - } + adj = state.getCurRawAdj(); + procState = state.getCurRawProcState(); + schedGroup = state.getCurrentSchedulingGroup(); + capability = state.getCurCapability(); } } } @@ -2603,97 +2330,27 @@ public class OomAdjuster { || procState > PROCESS_STATE_TOP); provi--) { ContentProviderRecord cpr = ppr.getProviderAt(provi); - for (int i = cpr.connections.size() - 1; - i >= 0 && (adj > FOREGROUND_APP_ADJ - || schedGroup == SCHED_GROUP_BACKGROUND - || procState > PROCESS_STATE_TOP); - i--) { - ContentProviderConnection conn = cpr.connections.get(i); - ProcessRecord client = conn.client; - final ProcessStateRecord cstate = client.mState; - if (client == app) { - // Being our own client is not interesting. - continue; - } - if (computeClients) { - computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true); - } else { - cstate.setCurRawAdj(cstate.getCurAdj()); - cstate.setCurRawProcState(cstate.getCurProcState()); - } - - if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { - continue; - } - - int clientAdj = cstate.getCurRawAdj(); - int clientProcState = cstate.getCurRawProcState(); - - // We always propagate PROCESS_CAPABILITY_BFSL to providers here, - // but, right before actually setting it to the process, - // we check the final procstate, and remove it if the procsate is below BFGS. - capability |= getBfslCapabilityFromClient(client); - - if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { - // If the other app is cached for any reason, for purposes here - // we are going to consider it empty. - clientProcState = PROCESS_STATE_CACHED_EMPTY; - } - if (client.mOptRecord.shouldNotFreeze()) { - // Propagate the shouldNotFreeze flag down the bindings. - app.mOptRecord.setShouldNotFreeze(true); - } - - boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp() - || clientProcState <= PROCESS_STATE_BOUND_TOP - || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE - && !cstate.isBackgroundRestricted()); - - String adjType = null; - if (adj > clientAdj) { - if (state.hasShownUi() && !state.getCachedIsHomeProcess() - && clientAdj > PERCEPTIBLE_APP_ADJ) { - adjType = "cch-ui-provider"; - } else { - adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); - state.setCurRawAdj(adj); - adjType = "provider"; - } - state.setCached(state.isCached() & cstate.isCached()); - } - - if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { - if (adjType == null) { - adjType = "provider"; - } - if (clientProcState == PROCESS_STATE_TOP) { - clientProcState = PROCESS_STATE_BOUND_TOP; - } else { - clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; - } - } + if (couldRecurse) { + // We're entering recursive functions below. + state.setCurRawAdj(adj); + state.setCurRawProcState(procState); + state.setCurrentSchedulingGroup(schedGroup); + state.setCurCapability(capability); + + for (int i = cpr.connections.size() - 1; + i >= 0 && (adj > FOREGROUND_APP_ADJ + || schedGroup == SCHED_GROUP_BACKGROUND + || procState > PROCESS_STATE_TOP); + i--) { + ContentProviderConnection conn = cpr.connections.get(i); + ProcessRecord client = conn.client; + computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll, + cycleReEval, computeClients, oomAdjReason, cachedAdj, true); - conn.trackProcState(clientProcState, mAdjSeq); - if (procState > clientProcState) { - procState = clientProcState; - state.setCurRawProcState(procState); - } - if (cstate.getCurrentSchedulingGroup() > schedGroup) { - schedGroup = SCHED_GROUP_DEFAULT; - } - if (adjType != null) { - state.setAdjType(adjType); - state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo - .REASON_PROVIDER_IN_USE); - state.setAdjSource(client); - state.setAdjSourceProcState(clientProcState); - state.setAdjTarget(cpr.name); - if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { - reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType - + ": " + app + ", due to " + client - + " adj=" + adj + " procState=" - + ProcessList.makeProcStateString(procState)); - } + adj = state.getCurRawAdj(); + procState = state.getCurRawProcState(); + schedGroup = state.getCurrentSchedulingGroup(); + capability = state.getCurCapability(); } } // If the provider has external (non-framework) process @@ -2799,7 +2456,7 @@ public class OomAdjuster { // restrictions on screen off if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE - && !scheduleLikeTopApp) { + && !state.shouldScheduleLikeTopApp()) { if (schedGroup > SCHED_GROUP_RESTRICTED) { schedGroup = SCHED_GROUP_RESTRICTED; } @@ -2817,6 +2474,7 @@ public class OomAdjuster { capability &= ~PROCESS_CAPABILITY_BFSL; } + state.setHasForegroundActivities(foregroundActivities); if (app.isPendingFinishAttach()) { // If the app is still starting up. We reset the computations to the @@ -2834,22 +2492,580 @@ public class OomAdjuster { // it when computing the final cached adj later. Note that we don't need to // worry about this for max adj above, since max adj will always be used to // keep it out of the cached vaues. - state.setCurAdj(adj); state.setCurCapability(capability); - state.setCurrentSchedulingGroup(schedGroup); - state.setCurProcState(procState); - state.setCurRawProcState(procState); state.updateLastInvisibleTime(hasVisibleActivities); - state.setHasForegroundActivities(foregroundActivities); state.setCompletedAdjSeq(mAdjSeq); - state.setCurBoundByNonBgRestrictedApp(boundByNonBgRestricted); + + schedGroup = setIntermediateAdjLSP(app, adj, prevAppAdj, schedGroup); + setIntermediateProcStateLSP(app, procState, prevProcState); + setIntermediateSchedGroupLSP(state, schedGroup); // if curAdj or curProcState improved, then this process was promoted return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState || state.getCurCapability() != prevCapability; } - private int getDefaultCapability(ProcessRecord app, int procState) { + /** + * @return The proposed change to the schedGroup. + */ + @GuardedBy({"mService", "mProcLock"}) + protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, + int schedGroup) { + final ProcessStateRecord state = app.mState; + state.setCurRawAdj(adj); + + adj = app.mServices.modifyRawOomAdj(adj); + if (adj > state.getMaxAdj()) { + adj = state.getMaxAdj(); + if (adj <= PERCEPTIBLE_LOW_APP_ADJ) { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + + state.setCurAdj(adj); + + return schedGroup; + } + + @GuardedBy({"mService", "mProcLock"}) + protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, + int prevProcState) { + final ProcessStateRecord state = app.mState; + state.setCurProcState(procState); + state.setCurRawProcState(procState); + } + + @GuardedBy({"mService", "mProcLock"}) + protected void setIntermediateSchedGroupLSP(ProcessStateRecord state, int schedGroup) { + // Put bound foreground services in a special sched group for additional + // restrictions on screen off + if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE + && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE + && !state.shouldScheduleLikeTopApp()) { + if (schedGroup > SCHED_GROUP_RESTRICTED) { + schedGroup = SCHED_GROUP_RESTRICTED; + } + } + + state.setCurrentSchedulingGroup(schedGroup); + } + + @GuardedBy({"mService", "mProcLock"}) + protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app, + ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, + boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, + boolean couldRecurse) { + if (app.isPendingFinishAttach()) { + // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. + return; + } + + final ProcessStateRecord state = app.mState; + ProcessStateRecord cstate = client.mState; + + if (couldRecurse) { + if (app.isSdkSandbox && cr.binding.attributedClient != null) { + // For SDK sandboxes, use the attributed client (eg the app that + // requested the sandbox) + client = cr.binding.attributedClient; + cstate = client.mState; + } + if (computeClients) { + computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true, + oomAdjReason, true); + } else { + cstate.setCurRawAdj(cstate.getCurAdj()); + cstate.setCurRawProcState(cstate.getCurProcState()); + } + } + + int clientAdj = cstate.getCurRawAdj(); + int clientProcState = cstate.getCurRawProcState(); + + final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP; + + int adj = state.getCurRawAdj(); + int procState = state.getCurRawProcState(); + int schedGroup = state.getCurrentSchedulingGroup(); + int capability = state.getCurCapability(); + + final int prevRawAdj = adj; + final int prevProcState = procState; + final int prevSchedGroup = schedGroup; + + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + + if (client.mOptRecord.shouldNotFreeze()) { + // Propagate the shouldNotFreeze flag down the bindings. + app.mOptRecord.setShouldNotFreeze(true); + } + + boolean trackedProcState = false; + + // We always propagate PROCESS_CAPABILITY_BFSL over bindings here, + // but, right before actually setting it to the process, + // we check the final procstate, and remove it if the procsate is below BFGS. + capability |= getBfslCapabilityFromClient(client); + + if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { + capability |= cstate.getCurCapability(); + } + + // If an app has network capability by default + // (by having procstate <= BFGS), then the apps it binds to will get + // elevated to a high enough procstate anyway to get network unless they + // request otherwise, so don't propagate the network capability by default + // in this case unless they explicitly request it. + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + // This is used to grant network access to Expedited Jobs. + if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) { + capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + } + } else { + capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; + } + } + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) { + // This is used to grant network access to User Initiated Jobs. + if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) { + capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + } + } + } + + if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { + return; + } + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. The specific cached state + // doesn't propagate except under certain conditions. + clientProcState = PROCESS_STATE_CACHED_EMPTY; + } + String adjType = null; + if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) { + // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen. + if (clientAdj < CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } + // Not doing bind OOM management, so treat + // this guy more like a started service. + if (state.hasShownUi() && !state.getCachedIsHomeProcess()) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-ui-services"; + } + state.setCached(false); + clientAdj = adj; + clientProcState = procState; + } else { + if (now >= (cr.binding.service.lastActivity + + mConstants.MAX_SERVICE_INACTIVITY)) { + // This service has not seen activity within + // recent memory, so allow it to drop to the + // LRU list if there is no other reason to keep + // it around. We'll also tag it with a label just + // to help debug and undertand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-services"; + } + clientAdj = adj; + } + } + } + if (adj > clientAdj) { + // If this process has recently shown UI, and + // the process that is binding to it is less + // important than being visible, then we don't + // care about the binding as much as we care + // about letting this process get into the LRU + // list to be killed and restarted if needed for + // memory. + if (state.hasShownUi() && !state.getCachedIsHomeProcess() + && clientAdj > PERCEPTIBLE_APP_ADJ) { + if (adj >= CACHED_APP_MIN_ADJ) { + adjType = "cch-bound-ui-services"; + } + } else { + int newAdj; + int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj. + if (cr.hasFlag(Context.BIND_ABOVE_CLIENT + | Context.BIND_IMPORTANT)) { + if (clientAdj >= PERSISTENT_SERVICE_ADJ) { + newAdj = clientAdj; + } else { + // make this service persistent + newAdj = PERSISTENT_SERVICE_ADJ; + schedGroup = SCHED_GROUP_DEFAULT; + procState = ActivityManager.PROCESS_STATE_PERSISTENT; + cr.trackProcState(procState, mAdjSeq); + trackedProcState = true; + } + } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE) + && clientAdj <= PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) { + newAdj = PERCEPTIBLE_LOW_APP_ADJ; + } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) + && cr.notHasFlag(Context.BIND_NOT_FOREGROUND) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { + // This is for user-initiated jobs. + // We use APP_ADJ + 1 here, so we can tell them apart from FGS. + newAdj = PERCEPTIBLE_APP_ADJ + 1; + } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE) + && cr.hasFlag(Context.BIND_NOT_FOREGROUND) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) { + // This is for expedited jobs. + // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart + // EJ and short-FGS. + newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2; + } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE) + && clientAdj < PERCEPTIBLE_APP_ADJ + && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) { + newAdj = PERCEPTIBLE_APP_ADJ; + } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) { + newAdj = clientAdj; + } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) + && clientAdj <= VISIBLE_APP_ADJ + && adj > VISIBLE_APP_ADJ) { + newAdj = VISIBLE_APP_ADJ; + } else { + if (adj > VISIBLE_APP_ADJ) { + // TODO: Is this too limiting for apps bound from TOP? + newAdj = Math.max(clientAdj, lbAdj); + } else { + newAdj = adj; + } + } + if (!cstate.isCached()) { + state.setCached(false); + } + if (adj > newAdj) { + adj = newAdj; + state.setCurRawAdj(adj); + adjType = "service"; + } + } + } + if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND + | Context.BIND_IMPORTANT_BACKGROUND)) { + // This will treat important bound services identically to + // the top app, which may behave differently than generic + // foreground work. + final int curSchedGroup = cstate.getCurrentSchedulingGroup(); + if (curSchedGroup > schedGroup) { + if (cr.hasFlag(Context.BIND_IMPORTANT)) { + schedGroup = curSchedGroup; + } else { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + if (clientProcState < PROCESS_STATE_TOP) { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best bound state after that. + if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; + } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else if (mService.mWakefulness.get() + == PowerManagerInternal.WAKEFULNESS_AWAKE + && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } else { + clientProcState = + PROCESS_STATE_IMPORTANT_FOREGROUND; + } + } else if (clientProcState == PROCESS_STATE_TOP) { + // Go at most to BOUND_TOP, unless requested to elevate + // to client's state. + clientProcState = PROCESS_STATE_BOUND_TOP; + final boolean enabled = cstate.getCachedCompatChange( + CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY); + if (enabled) { + if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { + // TOP process passes all capabilities to the service. + capability |= cstate.getCurCapability(); + } else { + // TOP process passes no capability to the service. + } + } else { + // TOP process passes all capabilities to the service. + capability |= cstate.getCurCapability(); + } + } + } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) { + if (clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND) { + clientProcState = + PROCESS_STATE_TRANSIENT_BACKGROUND; + } + } else { + if (clientProcState < PROCESS_STATE_IMPORTANT_BACKGROUND) { + clientProcState = + PROCESS_STATE_IMPORTANT_BACKGROUND; + } + } + + if (schedGroup < SCHED_GROUP_TOP_APP + && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) + && clientIsSystem) { + schedGroup = SCHED_GROUP_TOP_APP; + state.setScheduleLikeTopApp(true); + } + + if (!trackedProcState) { + cr.trackProcState(clientProcState, mAdjSeq); + } + + if (procState > clientProcState) { + procState = clientProcState; + state.setCurRawProcState(procState); + if (adjType == null) { + adjType = "service"; + } + } + if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND + && cr.hasFlag(Context.BIND_SHOWING_UI)) { + app.setPendingUiClean(true); + } + if (adjType != null) { + state.setAdjType(adjType); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(client); + state.setAdjSourceProcState(clientProcState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + } else { // BIND_WAIVE_PRIORITY == true + // BIND_WAIVE_PRIORITY bindings are special when it comes to the + // freezer. Processes bound via WPRI are expected to be running, + // but they are not promoted in the LRU list to keep them out of + // cached. As a result, they can freeze based on oom_adj alone. + // Normally, bindToDeath would fire when a cached app would die + // in the background, but nothing will fire when a running process + // pings a frozen process. Accordingly, any cached app that is + // bound by an unfrozen app via a WPRI binding has to remain + // unfrozen. + if (clientAdj < CACHED_APP_MIN_ADJ) { + app.mOptRecord.setShouldNotFreeze(true); + } + } + if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) { + app.mServices.setTreatLikeActivity(true); + if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY + && procState > PROCESS_STATE_CACHED_ACTIVITY) { + // This is a cached process, but somebody wants us to treat it like it has + // an activity, okay! + procState = PROCESS_STATE_CACHED_ACTIVITY; + state.setAdjType("cch-as-act"); + } + } + final ActivityServiceConnectionsHolder a = cr.activity; + if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) { + if (a != null && adj > FOREGROUND_APP_ADJ + && a.isActivityVisible()) { + adj = FOREGROUND_APP_ADJ; + state.setCurRawAdj(adj); + if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) { + if (cr.hasFlag(Context.BIND_IMPORTANT)) { + schedGroup = SCHED_GROUP_TOP_APP_BOUND; + } else { + schedGroup = SCHED_GROUP_DEFAULT; + } + } + state.setCached(false); + state.setAdjType("service"); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE); + state.setAdjSource(a); + state.setAdjSourceProcState(procState); + state.setAdjTarget(cr.binding.service.instanceName); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise to service w/activity: " + app); + } + } + } + + capability |= getDefaultCapability(app, procState); + + // Procstates below BFGS should never have this capability. + if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + capability &= ~PROCESS_CAPABILITY_BFSL; + } + + if (adj < prevRawAdj) { + schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); + } + if (procState < prevProcState) { + setIntermediateProcStateLSP(app, procState, prevProcState); + } + if (schedGroup > prevSchedGroup) { + setIntermediateSchedGroupLSP(state, schedGroup); + } + state.setCurCapability(capability); + + state.setEmpty(false); + } + + protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app, + ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, + boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj, + boolean couldRecurse) { + if (app.isPendingFinishAttach()) { + // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here. + return; + } + + final ProcessStateRecord state = app.mState; + final ProcessStateRecord cstate = client.mState; + + if (client == app) { + // Being our own client is not interesting. + return; + } + if (couldRecurse) { + if (computeClients) { + computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true, + oomAdjReason, true); + } else if (couldRecurse) { + cstate.setCurRawAdj(cstate.getCurAdj()); + cstate.setCurRawProcState(cstate.getCurProcState()); + } + + if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(), + cycleReEval)) { + return; + } + } + + int clientAdj = cstate.getCurRawAdj(); + int clientProcState = cstate.getCurRawProcState(); + + int adj = state.getCurRawAdj(); + int procState = state.getCurRawProcState(); + int schedGroup = state.getCurrentSchedulingGroup(); + int capability = state.getCurCapability(); + + final int prevRawAdj = adj; + final int prevProcState = procState; + final int prevSchedGroup = schedGroup; + + final int appUid = app.info.uid; + final int logUid = mService.mCurOomAdjUid; + + // We always propagate PROCESS_CAPABILITY_BFSL to providers here, + // but, right before actually setting it to the process, + // we check the final procstate, and remove it if the procsate is below BFGS. + capability |= getBfslCapabilityFromClient(client); + + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. + clientProcState = PROCESS_STATE_CACHED_EMPTY; + } + if (client.mOptRecord.shouldNotFreeze()) { + // Propagate the shouldNotFreeze flag down the bindings. + app.mOptRecord.setShouldNotFreeze(true); + } + + state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp() + || cstate.isCurBoundByNonBgRestrictedApp() + || clientProcState <= PROCESS_STATE_BOUND_TOP + || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE + && !cstate.isBackgroundRestricted())); + + String adjType = null; + if (adj > clientAdj) { + if (state.hasShownUi() && !state.getCachedIsHomeProcess() + && clientAdj > PERCEPTIBLE_APP_ADJ) { + adjType = "cch-ui-provider"; + } else { + adj = Math.max(clientAdj, FOREGROUND_APP_ADJ); + state.setCurRawAdj(adj); + adjType = "provider"; + } + state.setCached(state.isCached() & cstate.isCached()); + } + + if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) { + if (adjType == null) { + adjType = "provider"; + } + if (clientProcState == PROCESS_STATE_TOP) { + clientProcState = PROCESS_STATE_BOUND_TOP; + } else { + clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + } + } + + conn.trackProcState(clientProcState, mAdjSeq); + if (procState > clientProcState) { + procState = clientProcState; + state.setCurRawProcState(procState); + } + if (cstate.getCurrentSchedulingGroup() > schedGroup) { + schedGroup = SCHED_GROUP_DEFAULT; + } + if (adjType != null) { + state.setAdjType(adjType); + state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE); + state.setAdjSource(client); + state.setAdjSourceProcState(clientProcState); + state.setAdjTarget(conn.provider.name); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType + + ": " + app + ", due to " + client + + " adj=" + adj + " procState=" + + ProcessList.makeProcStateString(procState)); + } + } + + // Procstates below BFGS should never have this capability. + if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + capability &= ~PROCESS_CAPABILITY_BFSL; + } + + if (adj < prevRawAdj) { + schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup); + } + if (procState < prevProcState) { + setIntermediateProcStateLSP(app, procState, prevProcState); + } + if (schedGroup > prevSchedGroup) { + setIntermediateSchedGroupLSP(state, schedGroup); + } + state.setCurCapability(capability); + + state.setEmpty(false); + } + + protected int getDefaultCapability(ProcessRecord app, int procState) { final int networkCapabilities = NetworkPolicyManager.getDefaultProcessNetworkCapabilities(procState); final int baseCapabilities; @@ -2882,7 +3098,7 @@ public class OomAdjuster { /** * @return the BFSL capability from a client (of a service binding or provider). */ - int getBfslCapabilityFromClient(ProcessRecord client) { + protected int getBfslCapabilityFromClient(ProcessRecord client) { // Procstates above FGS should always have this flag. We shouldn't need this logic, // but let's do it just in case. if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) { @@ -2967,7 +3183,7 @@ public class OomAdjuster { /** Inform the oomadj observer of changes to oomadj. Used by tests. */ @GuardedBy("mService") - private void reportOomAdjMessageLocked(String tag, String msg) { + protected void reportOomAdjMessageLocked(String tag, String msg) { Slog.d(tag, msg); synchronized (mService.mOomAdjObserverLock) { if (mService.mCurOomAdjObserver != null) { @@ -2983,7 +3199,7 @@ public class OomAdjuster { /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */ @GuardedBy({"mService", "mProcLock"}) - private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now, + protected boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now, long nowElapsed, @OomAdjReason int oomAdjReson) { boolean success = true; final ProcessStateRecord state = app.mState; @@ -3272,6 +3488,8 @@ public class OomAdjuster { int initialCapability = PROCESS_CAPABILITY_NONE; boolean initialCached = true; final ProcessStateRecord state = app.mState; + final int prevProcState = PROCESS_STATE_UNKNOWN; + final int prevAdj = UNKNOWN_ADJ; // If the process has been marked as foreground, it is starting as the top app (with // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread. if (state.hasForegroundActivities()) { @@ -3306,6 +3524,9 @@ public class OomAdjuster { state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ); state.setForcingToImportant(null); state.setHasShownUi(false); + + onProcessStateChanged(app, prevProcState); + onProcessOomAdjChanged(app, prevAdj); } // ONLY used for unit testing in OomAdjusterTests.java @@ -3553,4 +3774,56 @@ public class OomAdjuster { } processes.clear(); } + + @GuardedBy("mService") + void onProcessBeginLocked(@NonNull ProcessRecord app) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @GuardedBy("mService") + void onProcessEndLocked(@NonNull ProcessRecord app) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + /** + * Called when the process state is changed outside of the OomAdjuster. + */ + @GuardedBy("mService") + void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + /** + * Called when the oom adj is changed outside of the OomAdjuster. + */ + @GuardedBy("mService") + void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @VisibleForTesting + void resetInternal() { + // Empty, the OomAdjusterModernImpl will have an implementation. + } + + @GuardedBy("mService") + protected int getInitialAdj(@NonNull ProcessRecord app) { + return app.mState.getCurAdj(); + } + + @GuardedBy("mService") + protected int getInitialProcState(@NonNull ProcessRecord app) { + return app.mState.getCurProcState(); + } + + @GuardedBy("mService") + protected int getInitialCapability(@NonNull ProcessRecord app) { + return app.mState.getCurCapability(); + } + + @GuardedBy("mService") + protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) { + // The caller will set the initial value in this implementation. + return app.mState.isCurBoundByNonBgRestrictedApp(); + } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md index 16091d1c162d..da5e12ef21fa 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.md +++ b/services/core/java/com/android/server/am/OomAdjuster.md @@ -130,3 +130,28 @@ The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current O * Iterate the processes from least important to most important ones. * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3. +## The Modern Implementation + +As aforementioned, the OomAdjuster makes the computation in a recursive way, while this is inefficient in dealing with the cycles. The overall code complexity should be around **O((1 + num(retries)) * num(procs) * num(binding connections))**. In addition, depending on the ordering of the input, the algorithm may produce different results and sometimes it's wrong. + +The new "Modern Implementation" is based on the rationale that, apps can't promote the service/provider it connects to, to a higher bucket than itself. We are introducing a bucket based, breadth first search algorithm, as illustrated below: + +``` +for all processes in the process list + compute the state of each process, but, excluding its clients + put each process to the corresponding bucket according to the state value +done + +for each bucket, starting from the top most to the bottom most + for each process in the bucket + for each process it binds to + if the state of the bindee process could be elevated because of the binding; then + move the bindee process to the higher bucket + fi + done + done +done +``` + +The overall code complexity should be around **O(num(procs) * num(binding connections))**, which saves the retry time from the existing algorithm. + diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java new file mode 100644 index 000000000000..b852ef56fceb --- /dev/null +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -0,0 +1,1125 @@ +/* + * 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.am; + +import static android.app.ActivityManager.PROCESS_STATE_BACKUP; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; +import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; +import static android.app.ActivityManager.PROCESS_STATE_HOME; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; +import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; +import static android.app.ActivityManager.PROCESS_STATE_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; +import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; +import static com.android.server.am.ProcessList.BACKUP_APP_ADJ; +import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; +import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; +import static com.android.server.am.ProcessList.HOME_APP_ADJ; +import static com.android.server.am.ProcessList.NATIVE_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; +import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; +import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; +import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; +import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; +import static com.android.server.am.ProcessList.SERVICE_ADJ; +import static com.android.server.am.ProcessList.SERVICE_B_ADJ; +import static com.android.server.am.ProcessList.SYSTEM_ADJ; +import static com.android.server.am.ProcessList.UNKNOWN_ADJ; +import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; +import android.content.pm.ServiceInfo; +import android.os.IBinder; +import android.os.SystemClock; +import android.os.Trace; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * A modern implementation of the oom adjuster. + */ +public class OomAdjusterModernImpl extends OomAdjuster { + static final String TAG = "OomAdjusterModernImpl"; + + // The ADJ_SLOT_INVALID is NOT an actual slot. + static final int ADJ_SLOT_INVALID = -1; + static final int ADJ_SLOT_NATIVE = 0; + static final int ADJ_SLOT_SYSTEM = 1; + static final int ADJ_SLOT_PERSISTENT_PROC = 2; + static final int ADJ_SLOT_PERSISTENT_SERVICE = 3; + static final int ADJ_SLOT_FOREGROUND_APP = 4; + static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5; + static final int ADJ_SLOT_VISIBLE_APP = 6; + static final int ADJ_SLOT_PERCEPTIBLE_APP = 7; + static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8; + static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9; + static final int ADJ_SLOT_BACKUP_APP = 10; + static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11; + static final int ADJ_SLOT_SERVICE = 12; + static final int ADJ_SLOT_HOME_APP = 13; + static final int ADJ_SLOT_PREVIOUS_APP = 14; + static final int ADJ_SLOT_SERVICE_B = 15; + static final int ADJ_SLOT_CACHED_APP = 16; + static final int ADJ_SLOT_UNKNOWN = 17; + + @IntDef(prefix = { "ADJ_SLOT_" }, value = { + ADJ_SLOT_INVALID, + ADJ_SLOT_NATIVE, + ADJ_SLOT_SYSTEM, + ADJ_SLOT_PERSISTENT_PROC, + ADJ_SLOT_PERSISTENT_SERVICE, + ADJ_SLOT_FOREGROUND_APP, + ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP, + ADJ_SLOT_VISIBLE_APP, + ADJ_SLOT_PERCEPTIBLE_APP, + ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP, + ADJ_SLOT_PERCEPTIBLE_LOW_APP, + ADJ_SLOT_BACKUP_APP, + ADJ_SLOT_HEAVY_WEIGHT_APP, + ADJ_SLOT_SERVICE, + ADJ_SLOT_HOME_APP, + ADJ_SLOT_PREVIOUS_APP, + ADJ_SLOT_SERVICE_B, + ADJ_SLOT_CACHED_APP, + ADJ_SLOT_UNKNOWN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AdjSlot{} + + static final int[] ADJ_SLOT_VALUES = new int[] { + NATIVE_ADJ, + SYSTEM_ADJ, + PERSISTENT_PROC_ADJ, + PERSISTENT_SERVICE_ADJ, + FOREGROUND_APP_ADJ, + PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ, + VISIBLE_APP_ADJ, + PERCEPTIBLE_APP_ADJ, + PERCEPTIBLE_MEDIUM_APP_ADJ, + PERCEPTIBLE_LOW_APP_ADJ, + BACKUP_APP_ADJ, + HEAVY_WEIGHT_APP_ADJ, + SERVICE_ADJ, + HOME_APP_ADJ, + PREVIOUS_APP_ADJ, + SERVICE_B_ADJ, + CACHED_APP_MIN_ADJ, + UNKNOWN_ADJ, + }; + + /** + * Note: Always use the raw adj to call this API. + */ + static @AdjSlot int adjToSlot(int adj) { + if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) { + // Conduct a binary search, in most of the cases it'll get a hit. + final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj); + if (index >= 0) { + return index; + } + // If not found, the returned index above should be (-(insertion point) - 1), + // let's return the first slot that's less than the adj value. + return -(index + 1) - 1; + } + return ADJ_SLOT_VALUES.length - 1; + } + + static final int[] PROC_STATE_SLOTS = new int[] { + PROCESS_STATE_PERSISTENT, // 0 + PROCESS_STATE_PERSISTENT_UI, + PROCESS_STATE_TOP, + PROCESS_STATE_BOUND_TOP, + PROCESS_STATE_FOREGROUND_SERVICE, + PROCESS_STATE_BOUND_FOREGROUND_SERVICE, + PROCESS_STATE_IMPORTANT_FOREGROUND, + PROCESS_STATE_IMPORTANT_BACKGROUND, + PROCESS_STATE_TRANSIENT_BACKGROUND, + PROCESS_STATE_BACKUP, + PROCESS_STATE_SERVICE, + PROCESS_STATE_RECEIVER, + PROCESS_STATE_TOP_SLEEPING, + PROCESS_STATE_HEAVY_WEIGHT, + PROCESS_STATE_HOME, + PROCESS_STATE_LAST_ACTIVITY, + PROCESS_STATE_CACHED_ACTIVITY, + PROCESS_STATE_CACHED_ACTIVITY_CLIENT, + PROCESS_STATE_CACHED_RECENT, + PROCESS_STATE_CACHED_EMPTY, + PROCESS_STATE_UNKNOWN, // -1 + }; + + static int processStateToSlot(@ActivityManager.ProcessState int state) { + if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) { + return state; + } + return PROC_STATE_SLOTS.length - 1; + } + + /** + * A container node in the {@link LinkedProcessRecordList}, + * holding the references to {@link ProcessRecord}. + */ + static class ProcessRecordNode { + static final int NODE_TYPE_PROC_STATE = 0; + static final int NODE_TYPE_ADJ = 1; + + @IntDef(prefix = { "NODE_TYPE_" }, value = { + NODE_TYPE_PROC_STATE, + NODE_TYPE_ADJ, + }) + @Retention(RetentionPolicy.SOURCE) + @interface NodeType {} + + static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1; + + @Nullable ProcessRecordNode mPrev; + @Nullable ProcessRecordNode mNext; + final @Nullable ProcessRecord mApp; + + ProcessRecordNode(@Nullable ProcessRecord app) { + mApp = app; + } + + void unlink() { + if (mPrev != null) { + mPrev.mNext = mNext; + } + if (mNext != null) { + mNext.mPrev = mPrev; + } + mPrev = mNext = null; + } + + boolean isLinked() { + return mPrev != null && mNext != null; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("ProcessRecordNode{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + sb.append(mApp); + sb.append(' '); + sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN); + sb.append(' '); + sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ); + sb.append(' '); + sb.append(Integer.toHexString(System.identityHashCode(mPrev))); + sb.append(' '); + sb.append(Integer.toHexString(System.identityHashCode(mNext))); + sb.append('}'); + return sb.toString(); + } + } + + private class ProcessRecordNodes { + private final @ProcessRecordNode.NodeType int mType; + + private final LinkedProcessRecordList[] mProcessRecordNodes; + // The last node besides the tail. + private final ProcessRecordNode[] mLastNode; + + ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) { + mType = type; + mProcessRecordNodes = new LinkedProcessRecordList[size]; + for (int i = 0; i < size; i++) { + mProcessRecordNodes[i] = new LinkedProcessRecordList(type); + } + mLastNode = new ProcessRecordNode[size]; + } + + int size() { + return mProcessRecordNodes.length; + } + + @VisibleForTesting + void reset() { + for (int i = 0; i < mProcessRecordNodes.length; i++) { + mProcessRecordNodes[i].reset(); + mLastNode[i] = null; + } + } + + void resetLastNodes() { + for (int i = 0; i < mProcessRecordNodes.length; i++) { + mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail(); + } + } + + void setLastNodeToHead(int slot) { + mLastNode[slot] = mProcessRecordNodes[slot].HEAD; + } + + void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) { + ProcessRecordNode node = mLastNode[slot].mNext; + final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL; + while (node != tail) { + mTmpOomAdjusterArgs.mApp = node.mApp; + // Save the next before calling callback, since that may change the node.mNext. + final ProcessRecordNode next = node.mNext; + callback.accept(mTmpOomAdjusterArgs); + // There are couple of cases: + // a) The current node is moved to another slot + // - for this case, we'd need to keep using the "next" node. + // b) There are one or more new nodes being appended to this slot + // - for this case, we'd need to make sure we scan the new node too. + // Based on the assumption that case a) is only possible with + // the computeInitialOomAdjLSP(), where the movings are for single node only, + // we may safely assume that, if the "next" used to be the "tail" here, and it's + // now a new tail somewhere else, that's case a); otherwise, it's case b); + node = next == tail && node.mNext != null && node.mNext.mNext != null + ? node.mNext : next; + } + } + + int getNumberOfSlots() { + return mProcessRecordNodes.length; + } + + void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + if (prevSlot != ADJ_SLOT_INVALID) { + if (mLastNode[prevSlot] == node) { + mLastNode[prevSlot] = node.mPrev; + } + node.unlink(); + } + mProcessRecordNodes[newSlot].append(node); + } + + void moveAllNodesTo(int fromSlot, int toSlot) { + final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot]; + final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot]; + if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) { + fromList.moveTo(toList); + mLastNode[fromSlot] = fromList.getLastNodeBeforeTail(); + } + } + + void moveAppToTail(ProcessRecord app) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + int slot; + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + slot = processStateToSlot(app.mState.getCurProcState()); + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + mProcessRecordNodes[slot].moveNodeToTail(node); + break; + case ProcessRecordNode.NODE_TYPE_ADJ: + slot = adjToSlot(app.mState.getCurRawAdj()); + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + mProcessRecordNodes[slot].moveNodeToTail(node); + break; + default: + return; + } + + } + + void reset(int slot) { + mProcessRecordNodes[slot].reset(); + } + + void unlink(@NonNull ProcessRecord app) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + final int slot = getCurrentSlot(app); + if (slot != ADJ_SLOT_INVALID) { + if (mLastNode[slot] == node) { + mLastNode[slot] = node.mPrev; + } + } + node.unlink(); + } + + void append(@NonNull ProcessRecord app) { + append(app, getCurrentSlot(app)); + } + + void append(@NonNull ProcessRecord app, int targetSlot) { + final ProcessRecordNode node = app.mLinkedNodes[mType]; + mProcessRecordNodes[targetSlot].append(node); + } + + private int getCurrentSlot(@NonNull ProcessRecord app) { + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + return processStateToSlot(app.mState.getCurProcState()); + case ProcessRecordNode.NODE_TYPE_ADJ: + return adjToSlot(app.mState.getCurRawAdj()); + } + return ADJ_SLOT_INVALID; + } + + String toString(int slot, int logUid) { + return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid); + } + + /** + * A simple version of {@link java.util.LinkedList}, as here we don't allocate new node + * while adding an object to it. + */ + private static class LinkedProcessRecordList { + // Sentinel head/tail, to make bookkeeping work easier. + final ProcessRecordNode HEAD = new ProcessRecordNode(null); + final ProcessRecordNode TAIL = new ProcessRecordNode(null); + final @ProcessRecordNode.NodeType int mNodeType; + + LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) { + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + mNodeType = nodeType; + } + + void append(@NonNull ProcessRecordNode node) { + node.mNext = TAIL; + node.mPrev = TAIL.mPrev; + TAIL.mPrev.mNext = node; + TAIL.mPrev = node; + } + + void moveTo(@NonNull LinkedProcessRecordList toList) { + if (HEAD.mNext != TAIL) { + toList.TAIL.mPrev.mNext = HEAD.mNext; + HEAD.mNext.mPrev = toList.TAIL.mPrev; + toList.TAIL.mPrev = TAIL.mPrev; + TAIL.mPrev.mNext = toList.TAIL; + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + } + } + + void moveNodeToTail(@NonNull ProcessRecordNode node) { + node.unlink(); + append(node); + } + + @NonNull ProcessRecordNode getLastNodeBeforeTail() { + return TAIL.mPrev; + } + + @VisibleForTesting + void reset() { + HEAD.mNext = TAIL; + TAIL.mPrev = HEAD; + } + + String toString(int logUid) { + final StringBuilder sb = new StringBuilder(); + sb.append("LinkedProcessRecordList{"); + sb.append(HEAD); + sb.append(' '); + sb.append(TAIL); + sb.append('['); + ProcessRecordNode node = HEAD.mNext; + while (node != TAIL) { + if (node.mApp != null && node.mApp.uid == logUid) { + sb.append(node); + sb.append(','); + } + node = node.mNext; + } + sb.append(']'); + sb.append('}'); + return sb.toString(); + } + } + } + + /** + * A data class for holding the parameters in computing oom adj. + */ + private class OomAdjusterArgs { + ProcessRecord mApp; + ProcessRecord mTopApp; + long mNow; + int mCachedAdj; + @OomAdjReason int mOomAdjReason; + @NonNull ActiveUids mUids; + boolean mFullUpdate; + + void update(ProcessRecord topApp, long now, int cachedAdj, + @OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) { + mTopApp = topApp; + mNow = now; + mCachedAdj = cachedAdj; + mOomAdjReason = oomAdjReason; + mUids = uids; + mFullUpdate = fullUpdate; + } + } + + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, + ActiveUids activeUids) { + this(service, processList, activeUids, createAdjusterThread()); + } + + OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList, + ActiveUids activeUids, ServiceThread adjusterThread) { + super(service, processList, activeUids, adjusterThread); + } + + private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes( + ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length); + private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes( + ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length); + private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs(); + + void linkProcessRecordToList(@NonNull ProcessRecord app) { + mProcessRecordProcStateNodes.append(app); + mProcessRecordAdjNodes.append(app); + } + + void unlinkProcessRecordFromList(@NonNull ProcessRecord app) { + mProcessRecordProcStateNodes.unlink(app); + mProcessRecordAdjNodes.unlink(app); + } + + @Override + @VisibleForTesting + void resetInternal() { + mProcessRecordProcStateNodes.reset(); + mProcessRecordAdjNodes.reset(); + } + + @GuardedBy("mService") + @Override + void onProcessBeginLocked(@NonNull ProcessRecord app) { + // Check one type should be good enough. + if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) { + for (int i = 0; i < app.mLinkedNodes.length; i++) { + app.mLinkedNodes[i] = new ProcessRecordNode(app); + } + } + if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) { + linkProcessRecordToList(app); + } + } + + @GuardedBy("mService") + @Override + void onProcessEndLocked(@NonNull ProcessRecord app) { + if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null + && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) { + unlinkProcessRecordFromList(app); + } + } + + @GuardedBy("mService") + @Override + void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) { + updateProcStateSlotIfNecessary(app, prevProcState); + } + + @GuardedBy("mService") + void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) { + updateAdjSlotIfNecessary(app, prevAdj); + } + + @GuardedBy("mService") + @Override + protected int getInitialAdj(@NonNull ProcessRecord app) { + return UNKNOWN_ADJ; + } + + @GuardedBy("mService") + @Override + protected int getInitialProcState(@NonNull ProcessRecord app) { + return PROCESS_STATE_UNKNOWN; + } + + @GuardedBy("mService") + @Override + protected int getInitialCapability(@NonNull ProcessRecord app) { + return 0; + } + + @GuardedBy("mService") + @Override + protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) { + return false; + } + + private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) { + if (app.mState.getCurRawAdj() != prevRawAdj) { + final int slot = adjToSlot(app.mState.getCurRawAdj()); + final int prevSlot = adjToSlot(prevRawAdj); + if (slot != prevSlot && slot != ADJ_SLOT_INVALID) { + mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + } + } + } + + private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) { + if (app.mState.getCurProcState() != prevProcState) { + final int slot = processStateToSlot(app.mState.getCurProcState()); + final int prevSlot = processStateToSlot(prevProcState); + if (slot != prevSlot) { + mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + } + } + } + + @Override + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + final ProcessRecord topApp = mService.getTopApp(); + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); + mAdjSeq++; + + final ProcessStateRecord state = app.mState; + final int oldAdj = state.getCurRawAdj(); + final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ + ? oldAdj : UNKNOWN_ADJ; + + final ActiveUids uids = mTmpUidRecords; + final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet; + final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList; + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + + uids.clear(); + targetProcesses.clear(); + targetProcesses.add(app); + reachableProcesses.clear(); + + // Find out all reachable processes from this app. + collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids); + + // Copy all of the reachable processes into the target process set. + targetProcesses.addAll(reachableProcesses); + reachableProcesses.clear(); + + final boolean result = performNewUpdateOomAdjLSP(oomAdjReason, + topApp, targetProcesses, uids, false, now, cachedAdj); + + reachableProcesses.addAll(targetProcesses); + assignCachedAdjIfNecessary(reachableProcesses); + for (int i = uids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = uids.valueAt(i); + uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); + } + updateUidsLSP(uids, nowElapsed); + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); + } + targetProcesses.clear(); + reachableProcesses.clear(); + + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + return result; + } + + @GuardedBy({"mService", "mProcLock"}) + @Override + protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles, + boolean startProfiling) { + final boolean fullUpdate = processes == null; + final ArrayList<ProcessRecord> activeProcesses = fullUpdate + ? mProcessList.getLruProcessesLOSP() : processes; + ActiveUids activeUids = uids; + if (activeUids == null) { + final int numUids = mActiveUids.size(); + activeUids = mTmpUidRecords; + activeUids.clear(); + for (int i = 0; i < numUids; i++) { + UidRecord uidRec = mActiveUids.valueAt(i); + activeUids.put(uidRec.getUid(), uidRec); + } + } + + if (startProfiling) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); + } + final long now = SystemClock.uptimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long oldTime = now - mConstants.mMaxEmptyTimeMillis; + final int numProc = activeProcesses.size(); + + mAdjSeq++; + if (fullUpdate) { + mNewNumServiceProcs = 0; + mNewNumAServiceProcs = 0; + } + + final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet; + targetProcesses.clear(); + if (!fullUpdate) { + targetProcesses.addAll(activeProcesses); + } + + performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids, + fullUpdate, now, UNKNOWN_ADJ); + + if (fullUpdate) { + assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); + } else { + activeProcesses.clear(); + activeProcesses.addAll(targetProcesses); + assignCachedAdjIfNecessary(activeProcesses); + + for (int i = activeUids.size() - 1; i >= 0; i--) { + final UidRecord uidRec = activeUids.valueAt(i); + uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP); + } + updateUidsLSP(activeUids, nowElapsed); + + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason); + } + + activeProcesses.clear(); + } + targetProcesses.clear(); + + if (startProfiling) { + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + return; + } + + /** + * Perform the oom adj update on the given {@code targetProcesses}. + * + * <p>Note: The expectation to the given {@code targetProcesses} is, the caller + * must have called {@link collectReachableProcessesLocked} on it. + */ + private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason, + ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids, + boolean fullUpdate, long now, int cachedAdj) { + + final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2; + clientProcesses.clear(); + + // We'll need to collect the upstream processes of the target apps here, because those + // processes would potentially impact the procstate/adj via bindings. + if (!fullUpdate) { + final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses, + clientProcesses); + + // If any of its upstream processes are in a cycle, + // move them into the candidate targets. + if (containsCycle) { + // Add all client apps to the target process list. + for (int i = 0, size = clientProcesses.size(); i < size; i++) { + final ProcessRecord client = clientProcesses.get(i); + final UidRecord uidRec = client.getUidRecord(); + targetProcesses.add(client); + if (uidRec != null) { + uids.put(uidRec.getUid(), uidRec); + } + } + clientProcesses.clear(); + } + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + final ProcessRecord app = targetProcesses.valueAt(i); + app.mState.resetCachedInfo(); + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + } else { + final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP(); + for (int i = 0, size = lru.size(); i < size; i++) { + final ProcessRecord app = lru.get(i); + app.mState.resetCachedInfo(); + final UidRecord uidRec = app.getUidRecord(); + if (uidRec != null) { + if (DEBUG_UID_OBSERVERS) { + Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec); + } + uidRec.reset(); + } + } + } + + updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids, + cachedAdj, now, fullUpdate); + + clientProcesses.clear(); + + return true; + } + + /** + * Collect the reversed reachable processes from the given {@code apps}, the result will be + * returned in the given {@code processes}, which will <em>NOT</em> include the processes from + * the given {@code apps}. + */ + @GuardedBy("mService") + private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps, + ArrayList<ProcessRecord> clientProcesses) { + final ArrayDeque<ProcessRecord> queue = mTmpQueue; + queue.clear(); + clientProcesses.clear(); + for (int i = 0, size = apps.size(); i < size; i++) { + final ProcessRecord app = apps.valueAt(i); + app.mState.setReachable(true); + app.mState.setReversedReachable(true); + queue.offer(app); + } + + // Track if any of them reachables could include a cycle + boolean containsCycle = false; + + // Scan upstreams of the process record + for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) { + if (!pr.mState.isReachable()) { + // If not in the given initial set of apps, add it. + clientProcesses.add(pr); + } + final ProcessServiceRecord psr = pr.mServices; + for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { + final ServiceRecord s = psr.getRunningServiceAt(i); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int j = serviceConnections.size() - 1; j >= 0; j--) { + final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); + for (int k = clist.size() - 1; k >= 0; k--) { + final ConnectionRecord cr = clist.get(k); + final ProcessRecord client = cr.binding.client; + containsCycle |= client.mState.isReversedReachable(); + if (client.mState.isReversedReachable()) { + continue; + } + queue.offer(client); + client.mState.setReversedReachable(true); + } + } + } + final ProcessProviderRecord ppr = pr.mProviders; + for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) { + final ContentProviderRecord cpr = ppr.getProviderAt(i); + for (int j = cpr.connections.size() - 1; j >= 0; j--) { + final ContentProviderConnection conn = cpr.connections.get(j); + final ProcessRecord client = conn.client; + containsCycle |= client.mState.isReversedReachable(); + if (client.mState.isReversedReachable()) { + continue; + } + queue.offer(client); + client.mState.setReversedReachable(true); + } + } + // If this process is a sandbox itself, also add the app on whose behalf + // its running + if (pr.isSdkSandbox) { + for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) { + ServiceRecord s = psr.getRunningServiceAt(is); + ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = + s.getConnections(); + for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { + ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); + for (int i = clist.size() - 1; i >= 0; i--) { + ConnectionRecord cr = clist.get(i); + ProcessRecord attributedApp = cr.binding.attributedClient; + if (attributedApp == null || attributedApp == pr) { + continue; + } + containsCycle |= attributedApp.mState.isReversedReachable(); + if (attributedApp.mState.isReversedReachable()) { + continue; + } + queue.offer(attributedApp); + attributedApp.mState.setReversedReachable(true); + } + } + } + } + } + + // Reset the temporary bits. + for (int i = clientProcesses.size() - 1; i >= 0; i--) { + clientProcesses.get(i).mState.setReversedReachable(false); + } + for (int i = 0, size = apps.size(); i < size; i++) { + final ProcessRecord app = apps.valueAt(i); + app.mState.setReachable(false); + app.mState.setReversedReachable(false); + } + return containsCycle; + } + + @GuardedBy({"mService", "mProcLock"}) + private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp, + ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses, + ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) { + mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate); + + mProcessRecordProcStateNodes.resetLastNodes(); + mProcessRecordAdjNodes.resetLastNodes(); + + final int procStateTarget = mProcessRecordProcStateNodes.size() - 1; + final int adjTarget = mProcessRecordAdjNodes.size() - 1; + + final int appUid = !fullUpdate && targetProcesses.size() > 0 + ? targetProcesses.valueAt(0).uid : -1; + final int logUid = mService.mCurOomAdjUid; + + mAdjSeq++; + // All apps to be updated will be moved to the lowest slot. + if (fullUpdate) { + // Move all the process record node to the lowest slot, we'll do recomputation on all of + // them. Use the processes from the lru list, because the scanning order matters here. + final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP(); + for (int i = procStateTarget; i >= 0; i--) { + mProcessRecordProcStateNodes.reset(i); + // Force the last node to the head since we'll recompute all of them. + mProcessRecordProcStateNodes.setLastNodeToHead(i); + } + // enqueue the targets in the reverse order of the lru list. + for (int i = lruList.size() - 1; i >= 0; i--) { + mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget); + } + // Do the same to the adj nodes. + for (int i = adjTarget; i >= 0; i--) { + mProcessRecordAdjNodes.reset(i); + // Force the last node to the head since we'll recompute all of them. + mProcessRecordAdjNodes.setLastNodeToHead(i); + } + for (int i = lruList.size() - 1; i >= 0; i--) { + mProcessRecordAdjNodes.append(lruList.get(i), adjTarget); + } + } else { + // Move the target processes to the lowest slot. + for (int i = 0, size = targetProcesses.size(); i < size; i++) { + final ProcessRecord app = targetProcesses.valueAt(i); + final int procStateSlot = processStateToSlot(app.mState.getCurProcState()); + final int adjSlot = adjToSlot(app.mState.getCurRawAdj()); + mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget); + mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget); + } + // Move the "lastNode" to head to make sure we scan all nodes in this slot. + mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget); + mProcessRecordAdjNodes.setLastNodeToHead(adjTarget); + } + + // All apps to be updated have been moved to the lowest slot. + // Do an initial pass of the computation. + mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1, + this::computeInitialOomAdjLSP); + + if (!fullUpdate) { + // We didn't update the client processes with the computeInitialOomAdjLSP + // because they don't need to do so. But they'll be playing vital roles in + // computing the bindings. So include them into the scan list below. + for (int i = 0, size = clientProcesses.size(); i < size; i++) { + mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i)); + } + // We don't update the adj list since we're resetting it below. + } + + // Now nodes are set into their slots, without facting in the bindings. + // The nodes between the `lastNode` pointer and the TAIL should be the new nodes. + // + // The whole rationale here is that, the bindings from client to host app, won't elevate + // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT + // is a special case here, but client app's raw adj is still no less than the host app's). + // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes, + // check its bindings, elevate its host app's slot if necessary. + // + // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list. + // Because the procstate and adj are not always in sync - there are cases where + // the processes with lower proc state could be getting a higher oom adj score. + // And because of this, the procstate and adj node lists are basically two priority heaps. + // + // As the 2nd pass with the adj node lists potentially includes a significant amount of + // duplicated scans as the 1st pass has done, we'll reset the last node pointers for + // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot + // gets bumped, we'll only scan those in 2nd pass. + + mProcessRecordAdjNodes.resetLastNodes(); + + // 1st pass, scan each slot in the procstate node list. + for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) { + mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP); + } + + // 2nd pass, scan each slot in the adj node list. + for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) { + mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP); + } + } + + @GuardedBy({"mService", "mProcLock"}) + private void computeInitialOomAdjLSP(OomAdjusterArgs args) { + final ProcessRecord app = args.mApp; + final int cachedAdj = args.mCachedAdj; + final ProcessRecord topApp = args.mTopApp; + final long now = args.mNow; + final int oomAdjReason = args.mOomAdjReason; + final ActiveUids uids = args.mUids; + final boolean fullUpdate = args.mFullUpdate; + + if (DEBUG_OOM_ADJ) { + Slog.i(TAG, "OOM ADJ initial args app=" + app + + " cachedAdj=" + cachedAdj + + " topApp=" + topApp + + " now=" + now + + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason) + + " fullUpdate=" + fullUpdate); + } + + if (uids != null) { + final UidRecord uidRec = app.getUidRecord(); + + if (uidRec != null) { + uids.put(uidRec.getUid(), uidRec); + } + } + + computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason, + false); + } + + /** + * @return The proposed change to the schedGroup. + */ + @GuardedBy({"mService", "mProcLock"}) + @Override + protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, + int schedGroup) { + schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup); + + updateAdjSlotIfNecessary(app, prevRawAppAdj); + + return schedGroup; + } + + @GuardedBy({"mService", "mProcLock"}) + @Override + protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, + int prevProcState) { + super.setIntermediateProcStateLSP(app, procState, prevProcState); + + updateProcStateSlotIfNecessary(app, prevProcState); + } + + @GuardedBy({"mService", "mProcLock"}) + private void computeHostOomAdjLSP(OomAdjusterArgs args) { + final ProcessRecord app = args.mApp; + final int cachedAdj = args.mCachedAdj; + final ProcessRecord topApp = args.mTopApp; + final long now = args.mNow; + final @OomAdjReason int oomAdjReason = args.mOomAdjReason; + final boolean fullUpdate = args.mFullUpdate; + final ActiveUids uids = args.mUids; + + final ProcessServiceRecord psr = app.mServices; + for (int i = psr.numberOfConnections() - 1; i >= 0; i--) { + ConnectionRecord cr = psr.getConnectionAt(i); + ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS) + ? cr.binding.service.isolationHostProc : cr.binding.service.app; + if (service == null || service == app + || (service.mState.getMaxAdj() >= SYSTEM_ADJ + && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + + computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + + for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) { + final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i); + final ProcessRecord service = cr.binding.service.app; + if (service == null || service == app + || (service.mState.getMaxAdj() >= SYSTEM_ADJ + && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + + final ProcessProviderRecord ppr = app.mProviders; + for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) { + ContentProviderConnection cpc = ppr.getProviderConnectionAt(i); + ProcessRecord provider = cpc.provider.proc; + if (provider == null || provider == app + || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ + && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ) + || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ + && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND + && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + continue; + } + + computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false, + oomAdjReason, cachedAdj, false); + } + } +} diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index f532122c10d9..7037fecb2713 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -22,6 +22,7 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.MY_PID; +import static com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode.NUM_NODE_TYPE; import static java.util.Objects.requireNonNull; @@ -63,6 +64,7 @@ import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.os.Zygote; import com.android.server.FgThread; +import com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode; import com.android.server.wm.WindowProcessController; import com.android.server.wm.WindowProcessListener; @@ -434,6 +436,8 @@ class ProcessRecord implements WindowProcessListener { */ volatile boolean mSkipProcessGroupCreation; + final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE]; + void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startUptime, long startElapsedTime) { this.mStartUid = startUid; @@ -1114,6 +1118,7 @@ class ProcessRecord implements WindowProcessListener { mState.onCleanupApplicationRecordLSP(); mServices.onCleanupApplicationRecordLocked(); mReceivers.onCleanupApplicationRecordLocked(); + mService.mOomAdjuster.onProcessEndLocked(this); return mProviders.onCleanupApplicationRecordLocked(allowRestart); } diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 7ff6d116baaf..a165e8897aa4 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.pm.ServiceInfo; @@ -134,6 +135,11 @@ final class ProcessServiceRecord { private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>(); /** + * All ConnectionRecord this process holds indirectly to SDK sandbox processes. + */ + private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections; + + /** * A set of UIDs of all bound clients. */ private ArraySet<Integer> mBoundClientUids = new ArraySet<>(); @@ -490,13 +496,18 @@ final class ProcessServiceRecord { void addConnection(ConnectionRecord connection) { mConnections.add(connection); + addSdkSandboxConnectionIfNecessary(connection); } void removeConnection(ConnectionRecord connection) { mConnections.remove(connection); + removeSdkSandboxConnectionIfNecessary(connection); } void removeAllConnections() { + for (int i = 0, size = mConnections.size(); i < size; i++) { + removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i)); + } mConnections.clear(); } @@ -508,6 +519,39 @@ final class ProcessServiceRecord { return mConnections.size(); } + private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) { + final ProcessRecord attributedClient = connection.binding.attributedClient; + if (attributedClient != null && connection.binding.service.isSdkSandbox) { + if (attributedClient.mServices.mSdkSandboxConnections == null) { + attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>(); + } + attributedClient.mServices.mSdkSandboxConnections.add(connection); + } + } + + private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) { + final ProcessRecord attributedClient = connection.binding.attributedClient; + if (attributedClient != null && connection.binding.service.isSdkSandbox) { + if (attributedClient.mServices.mSdkSandboxConnections == null) { + attributedClient.mServices.mSdkSandboxConnections.remove(connection); + } + } + } + + void removeAllSdkSandboxConnections() { + if (mSdkSandboxConnections != null) { + mSdkSandboxConnections.clear(); + } + } + + ConnectionRecord getSdkSandboxConnectionAt(int index) { + return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null; + } + + int numberOfSdkSandboxConnections() { + return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0; + } + void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) { mBoundClientUids.add(clientUid); mApp.getWindowProcessController() diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index db341d253818..a9c388c232ed 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -378,6 +378,12 @@ final class ProcessStateRecord { private boolean mReachable; /** + * Whether or not this process is reversed reachable from given process. + */ + @GuardedBy("mService") + private boolean mReversedReachable; + + /** * The most recent time when the last visible activity within this process became invisible. * * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is @@ -454,6 +460,9 @@ final class ProcessStateRecord { @GuardedBy("mService") private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND; + @GuardedBy("mService") + private boolean mScheduleLikeTopApp = false; + ProcessStateRecord(ProcessRecord app) { mApp = app; mService = app.mService; @@ -614,9 +623,11 @@ final class ProcessStateRecord { void forceProcessStateUpTo(int newState) { if (mRepProcState > newState) { synchronized (mProcLock) { + final int prevProcState = mRepProcState; setReportedProcState(newState); setCurProcState(newState); setCurRawProcState(newState); + mService.mOomAdjuster.onProcessStateChanged(mApp, prevProcState); } } } @@ -985,6 +996,16 @@ final class ProcessStateRecord { } @GuardedBy("mService") + boolean isReversedReachable() { + return mReversedReachable; + } + + @GuardedBy("mService") + void setReversedReachable(boolean reversedReachable) { + mReversedReachable = reversedReachable; + } + + @GuardedBy("mService") void resetCachedInfo() { mCachedHasActivities = VALUE_INVALID; mCachedIsHeavyWeight = VALUE_INVALID; @@ -1134,6 +1155,16 @@ final class ProcessStateRecord { return mCachedSchedGroup; } + @GuardedBy("mService") + boolean shouldScheduleLikeTopApp() { + return mScheduleLikeTopApp; + } + + @GuardedBy("mService") + void setScheduleLikeTopApp(boolean scheduleLikeTopApp) { + mScheduleLikeTopApp = scheduleLikeTopApp; + } + @GuardedBy(anyOf = {"mService", "mProcLock"}) public String makeAdjReason() { if (mAdjSource != null || mAdjTarget != null) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index aabab61c36f4..f7bbc8b50bc0 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -1324,7 +1324,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN }); } - public void postNotification() { + public void postNotification(boolean byForegroundService) { if (isForeground && foregroundNoti != null && app != null) { final int appUid = appInfo.uid; final int appPid = app.getPid(); @@ -1432,7 +1432,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } nm.enqueueNotification(localPackageName, localPackageName, appUid, appPid, null, localForegroundId, localForegroundNoti, - userId); + userId, byForegroundService /* byForegroundService */); foregroundNoti = localForegroundNoti; // save it for amending next time diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java new file mode 100644 index 000000000000..683b3eb7a92e --- /dev/null +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -0,0 +1,204 @@ +/* + * 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.audio; + +import static android.media.AudioSystem.DEVICE_NONE; +import static android.media.AudioSystem.isBluetoothDevice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Objects; + +/** + * Class representing all devices that were previously or are currently connected. Data is + * persisted in {@link android.provider.Settings.Secure} + */ +/*package*/ final class AdiDeviceState { + private static final String TAG = "AS.AdiDeviceState"; + + private static final String SETTING_FIELD_SEPARATOR = ","; + + @AudioDeviceInfo.AudioDeviceType + private final int mDeviceType; + + private final int mInternalDeviceType; + @NonNull + private final String mDeviceAddress; + private boolean mSAEnabled; + private boolean mHasHeadTracker = false; + private boolean mHeadTrackerEnabled; + + /** + * Constructor + * + * @param deviceType external audio device type + * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the + * default conversion of the external type will be used + * @param address must be non-null for wireless devices + * @throws NullPointerException if a null address is passed for a wireless device + */ + AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, + int internalDeviceType, + @Nullable String address) { + mDeviceType = deviceType; + if (internalDeviceType != DEVICE_NONE) { + mInternalDeviceType = internalDeviceType; + } else { + mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType); + + } + mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull( + address) : ""; + } + + @AudioDeviceInfo.AudioDeviceType + public int getDeviceType() { + return mDeviceType; + } + + public int getInternalDeviceType() { + return mInternalDeviceType; + } + + @NonNull + public String getDeviceAddress() { + return mDeviceAddress; + } + + public void setSAEnabled(boolean sAEnabled) { + mSAEnabled = sAEnabled; + } + + public boolean isSAEnabled() { + return mSAEnabled; + } + + public void setHeadTrackerEnabled(boolean headTrackerEnabled) { + mHeadTrackerEnabled = headTrackerEnabled; + } + + public boolean isHeadTrackerEnabled() { + return mHeadTrackerEnabled; + } + + public void setHasHeadTracker(boolean hasHeadTracker) { + mHasHeadTracker = hasHeadTracker; + } + + + public boolean hasHeadTracker() { + return mHasHeadTracker; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + // type check and cast + if (getClass() != obj.getClass()) { + return false; + } + final AdiDeviceState sads = (AdiDeviceState) obj; + return mDeviceType == sads.mDeviceType + && mInternalDeviceType == sads.mInternalDeviceType + && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull + && mSAEnabled == sads.mSAEnabled + && mHasHeadTracker == sads.mHasHeadTracker + && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; + } + + @Override + public int hashCode() { + return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled, + mHasHeadTracker, mHeadTrackerEnabled); + } + + @Override + public String toString() { + return "type: " + mDeviceType + "internal type: " + mInternalDeviceType + + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled + + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; + } + + public String toPersistableString() { + return (new StringBuilder().append(mDeviceType) + .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) + .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0") + .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") + .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") + .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) + .toString()); + } + + /** + * Gets the max size (including separators) when persisting the elements with + * {@link AdiDeviceState#toPersistableString()}. + */ + public static int getPeristedMaxSize() { + return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 + + (SETTINGS_FIELD_SEPARATOR)5 */ + } + + @Nullable + public static AdiDeviceState fromPersistedString(@Nullable String persistedString) { + if (persistedString == null) { + return null; + } + if (persistedString.isEmpty()) { + return null; + } + String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); + // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal + // device type + if (fields.length != 5 && fields.length != 6) { + // expecting all fields, fewer may mean corruption, ignore those settings + return null; + } + try { + final int deviceType = Integer.parseInt(fields[0]); + int internalDeviceType = -1; + if (fields.length == 6) { + internalDeviceType = Integer.parseInt(fields[5]); + } + final AdiDeviceState deviceState = new AdiDeviceState(deviceType, + internalDeviceType, fields[1]); + deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1); + deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); + deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); + return deviceState; + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); + return null; + } + } + + public AudioDeviceAttributes getAudioDeviceAttributes() { + return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, + mDeviceType, mDeviceAddress); + } + +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index e4dd4a40d10c..946f01688e66 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -52,6 +52,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.util.PrintWriterPrinter; @@ -71,8 +72,11 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -/** @hide */ -/*package*/ final class AudioDeviceBroker { +/** + * @hide + * (non final for mocking/spying) + */ +public class AudioDeviceBroker { private static final String TAG = "AS.AudioDeviceBroker"; @@ -1893,7 +1897,6 @@ import java.util.concurrent.atomic.AtomicBoolean; final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); } break; - case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: { synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { @@ -1901,7 +1904,9 @@ import java.util.concurrent.atomic.AtomicBoolean; } } } break; - + case MSG_PERSIST_AUDIO_DEVICE_SETTINGS: + onPersistAudioDeviceSettings(); + break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1980,6 +1985,8 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52; private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53; + private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: @@ -2471,4 +2478,95 @@ import java.util.concurrent.atomic.AtomicBoolean; info.getId(), null /*mixerAttributes*/); } + + /** + * post a message to persist the audio device settings. + * Message is delayed by 1s on purpose in case of successive changes in quick succession (at + * init time for instance) + * Note this method is made public to work around a Mockito bug where it needs to be public + * in order to be mocked by a test a the same package + * (see https://code.google.com/archive/p/mockito/issues/127) + */ + public void persistAudioDeviceSettings() { + sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000); + } + + void onPersistAudioDeviceSettings() { + final String deviceSettings = mDeviceInventory.getDeviceSettings(); + Log.v(TAG, "saving audio device settings: " + deviceSettings); + final SettingsAdapter settings = mAudioService.getSettings(); + boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(), + Settings.Secure.AUDIO_DEVICE_INVENTORY, + deviceSettings, UserHandle.USER_CURRENT); + if (!res) { + Log.e(TAG, "error saving audio device settings: " + deviceSettings); + } + } + + void onReadAudioDeviceSettings() { + final SettingsAdapter settingsAdapter = mAudioService.getSettings(); + final ContentResolver contentResolver = mAudioService.getContentResolver(); + String settings = settingsAdapter.getSecureStringForUser(contentResolver, + Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT); + if (settings == null) { + Log.i(TAG, "reading spatial audio device settings from legacy key" + + Settings.Secure.SPATIAL_AUDIO_ENABLED); + // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like + // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid + // device settings when calling {@link #setDeviceSettings()} + settings = settingsAdapter.getSecureStringForUser(contentResolver, + Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); + if (settings == null) { + Log.i(TAG, "no spatial audio device settings stored with legacy key"); + } else if (!settings.equals("")) { + // Delete old key value and update the new key + if (!settingsAdapter.putSecureStringForUser(contentResolver, + Settings.Secure.SPATIAL_AUDIO_ENABLED, + /*value=*/"", + UserHandle.USER_CURRENT)) { + Log.w(TAG, "cannot erase the legacy audio device settings with key " + + Settings.Secure.SPATIAL_AUDIO_ENABLED); + } + if (!settingsAdapter.putSecureStringForUser(contentResolver, + Settings.Secure.AUDIO_DEVICE_INVENTORY, + settings, + UserHandle.USER_CURRENT)) { + Log.e(TAG, "error updating the new audio device settings with key " + + Settings.Secure.AUDIO_DEVICE_INVENTORY); + } + } + } + + if (settings != null && !settings.equals("")) { + setDeviceSettings(settings); + } + } + + void setDeviceSettings(String settings) { + mDeviceInventory.setDeviceSettings(settings); + } + + /** Test only method. */ + String getDeviceSettings() { + return mDeviceInventory.getDeviceSettings(); + } + + List<AdiDeviceState> getImmutableDeviceInventory() { + return mDeviceInventory.getImmutableDeviceInventory(); + } + + void addDeviceStateToInventory(AdiDeviceState deviceState) { + mDeviceInventory.addDeviceStateToInventory(deviceState); + } + + AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, + int canonicalType) { + return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType); + } + + //------------------------------------------------ + // for testing purposes only + void clearDeviceInventory() { + mDeviceInventory.clearDeviceInventory(); + } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index b70e11ddcd91..f5b7ecf5daf4 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,6 +15,8 @@ */ package com.android.server.audio; +import static android.media.AudioSystem.isBluetoothDevice; + import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; @@ -78,12 +80,51 @@ public class AudioDeviceInventory { private static final String TAG = "AS.AudioDeviceInventory"; + private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; + private static final String SETTING_DEVICE_SEPARATOR = "\\|"; + // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices private final Object mDevicesLock = new Object(); //Audio Analytics ids. private static final String mMetricsId = "audio.device."; + private final Object mDeviceInventoryLock = new Object(); + @GuardedBy("mDeviceCatalogLock") + private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0); + List<AdiDeviceState> getImmutableDeviceInventory() { + synchronized (mDeviceInventoryLock) { + return List.copyOf(mDeviceInventory); + } + } + + void addDeviceStateToInventory(AdiDeviceState deviceState) { + synchronized (mDeviceInventoryLock) { + mDeviceInventory.add(deviceState); + } + } + + AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, + int canonicalDeviceType) { + final boolean isWireless = isBluetoothDevice(ada.getInternalType()); + synchronized (mDeviceInventoryLock) { + for (AdiDeviceState deviceSetting : mDeviceInventory) { + if (deviceSetting.getDeviceType() == canonicalDeviceType + && (!isWireless || ada.getAddress().equals( + deviceSetting.getDeviceAddress()))) { + return deviceSetting; + } + } + } + return null; + } + + void clearDeviceInventory() { + synchronized (mDeviceInventoryLock) { + mDeviceInventory.clear(); + } + } + // List of connected devices // Key for map created from DeviceInfo.makeDeviceListKey() @GuardedBy("mDevicesLock") @@ -341,6 +382,12 @@ public class AudioDeviceInventory { mAppliedPresetRolesInt.forEach((key, devices) -> { pw.println(" " + prefix + "preset: " + key.first + " role:" + key.second + " devices:" + devices); }); + pw.println("\ndevices:\n"); + synchronized (mDeviceInventoryLock) { + for (AdiDeviceState device : mDeviceInventory) { + pw.println("\t" + device + "\n"); + } + } } //------------------------------------------------------------ @@ -1198,7 +1245,7 @@ public class AudioDeviceInventory { AudioDeviceInfo device = Stream.of(connectedDevices) .filter(d -> d.getInternalType() == ada.getInternalType()) - .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType()) + .filter(d -> (!isBluetoothDevice(d.getInternalType()) || (d.getAddress().equals(ada.getAddress())))) .findFirst() .orElse(null); @@ -1621,7 +1668,7 @@ public class AudioDeviceInventory { } for (DeviceInfo di : mConnectedDevices.values()) { - if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) { + if (!isBluetoothDevice(di.mDeviceType)) { continue; } AudioDeviceAttributes ada = @@ -1735,7 +1782,7 @@ public class AudioDeviceInventory { } HashSet<String> processedAddresses = new HashSet<>(0); for (DeviceInfo di : mConnectedDevices.values()) { - if (!AudioSystem.isBluetoothDevice(di.mDeviceType) + if (!isBluetoothDevice(di.mDeviceType) || processedAddresses.contains(di.mDeviceAddress)) { continue; } @@ -1745,7 +1792,7 @@ public class AudioDeviceInventory { + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles); } for (DeviceInfo di2 : mConnectedDevices.values()) { - if (!AudioSystem.isBluetoothDevice(di2.mDeviceType) + if (!isBluetoothDevice(di2.mDeviceType) || !di.mDeviceAddress.equals(di2.mDeviceAddress)) { continue; } @@ -2372,6 +2419,40 @@ public class AudioDeviceInventory { } } + /*package*/ String getDeviceSettings() { + int deviceCatalogSize = 0; + synchronized (mDeviceInventoryLock) { + deviceCatalogSize = mDeviceInventory.size(); + } + final StringBuilder settingsBuilder = new StringBuilder( + deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); + + synchronized (mDeviceInventoryLock) { + for (int i = 0; i < mDeviceInventory.size(); i++) { + settingsBuilder.append(mDeviceInventory.get(i).toPersistableString()); + if (i != mDeviceInventory.size() - 1) { + settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); + } + } + } + return settingsBuilder.toString(); + } + + /*package*/ void setDeviceSettings(String settings) { + clearDeviceInventory(); + String[] devSettings = TextUtils.split(Objects.requireNonNull(settings), + SETTING_DEVICE_SEPARATOR); + // small list, not worth overhead of Arrays.stream(devSettings) + for (String setting : devSettings) { + AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting); + // Note if the device is not compatible with spatialization mode or the device + // type is not canonical, it will be ignored in {@link SpatializerHelper}. + if (devState != null) { + addDeviceStateToInventory(devState); + } + } + } + //---------------------------------------------------------- // For tests only diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3ffca0e22fc0..3353b9ec538f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -383,7 +383,6 @@ public class AudioService extends IAudioService.Stub private static final int MSG_DISPATCH_AUDIO_MODE = 40; private static final int MSG_ROUTING_UPDATED = 41; private static final int MSG_INIT_HEADTRACKING_SENSORS = 42; - private static final int MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS = 43; private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44; private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45; private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46; @@ -1057,6 +1056,8 @@ public class AudioService extends IAudioService.Stub mAudioPolicy = audioPolicy; mPlatformType = AudioSystem.getPlatformType(context); + mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem); + mIsSingleVolume = AudioSystem.isSingleVolume(context); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -1069,13 +1070,14 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase)); - final boolean binauralEnabledDefault = SystemProperties.getBoolean( + boolean binauralEnabledDefault = SystemProperties.getBoolean( "ro.audio.spatializer_binaural_enabled_default", true); - final boolean transauralEnabledDefault = SystemProperties.getBoolean( + boolean transauralEnabledDefault = SystemProperties.getBoolean( "ro.audio.spatializer_transaural_enabled_default", true); - final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean( + boolean headTrackingEnabledDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default); - mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, + + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, mDeviceBroker, binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); @@ -1243,8 +1245,6 @@ public class AudioService extends IAudioService.Stub mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); - mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem); - mRecordMonitor = new RecordingActivityMonitor(mContext); mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true); @@ -6618,6 +6618,10 @@ public class AudioService extends IAudioService.Stub return mContentResolver; } + /*package*/ SettingsAdapter getSettings() { + return mSettings; + } + /////////////////////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////////////////////// @@ -9258,10 +9262,6 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.onInitSensors(); break; - case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS: - onPersistSpatialAudioDeviceSettings(); - break; - case MSG_RESET_SPATIALIZER: mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect); break; @@ -10323,41 +10323,11 @@ public class AudioService extends IAudioService.Stub } void onInitSpatializer() { - final String settings = mSettings.getSecureStringForUser(mContentResolver, - Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); - if (settings == null) { - Log.e(TAG, "error reading spatial audio device settings"); - } - mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings); + mDeviceBroker.onReadAudioDeviceSettings(); + mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } - /** - * post a message to persist the spatial audio device settings. - * Message is delayed by 1s on purpose in case of successive changes in quick succession (at - * init time for instance) - * Note this method is made public to work around a Mockito bug where it needs to be public - * in order to be mocked by a test a the same package - * (see https://code.google.com/archive/p/mockito/issues/127) - */ - public void persistSpatialAudioDeviceSettings() { - sendMsg(mAudioHandler, - MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS, - SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG, - /*delay*/ 1000); - } - - void onPersistSpatialAudioDeviceSettings() { - final String settings = mSpatializerHelper.getSADeviceSettings(); - Log.v(TAG, "saving spatial audio device settings: " + settings); - boolean res = mSettings.putSecureStringForUser(mContentResolver, - Settings.Secure.SPATIAL_AUDIO_ENABLED, - settings, UserHandle.USER_CURRENT); - if (!res) { - Log.e(TAG, "error saving spatial audio device settings: " + settings); - } - } - //========================================================================================== // camera sound is forced if any of the resources corresponding to one active SIM diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 462c9381b904..969dd60a8012 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -16,6 +16,8 @@ package com.android.server.audio; +import static android.media.AudioSystem.isBluetoothDevice; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -47,13 +49,13 @@ import android.util.Pair; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.UUID; /** @@ -73,11 +75,12 @@ public class SpatializerHelper { private final @NonNull AudioSystemAdapter mASA; private final @NonNull AudioService mAudioService; + private final @NonNull AudioDeviceBroker mDeviceBroker; private @Nullable SensorManager mSensorManager; //------------------------------------------------------------ - private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) { + /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) { { append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); @@ -98,13 +101,6 @@ public class SpatializerHelper { } }; - private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO, - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - AudioDeviceInfo.TYPE_BLE_HEADSET, - AudioDeviceInfo.TYPE_BLE_SPEAKER, - AudioDeviceInfo.TYPE_BLE_BROADCAST - }; - // Spatializer state machine /*package*/ static final int STATE_UNINITIALIZED = 0; /*package*/ static final int STATE_NOT_SUPPORTED = 1; @@ -114,10 +110,15 @@ public class SpatializerHelper { /*package*/ static final int STATE_DISABLED_AVAILABLE = 6; private int mState = STATE_UNINITIALIZED; + @VisibleForTesting boolean mBinauralEnabledDefault; + @VisibleForTesting boolean mTransauralEnabledDefault; + @VisibleForTesting boolean mHeadTrackingEnabledDefault; + private boolean mFeatureEnabled = false; /** current level as reported by native Spatializer in callback */ private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + private boolean mTransauralSupported = false; private boolean mBinauralSupported = false; private boolean mIsHeadTrackingSupported = false; @@ -160,31 +161,21 @@ public class SpatializerHelper { */ private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0); - /** - * List of devices where Spatial Audio is possible. Each device can be enabled or disabled - * (== user choice to use or not) - */ - @GuardedBy("this") - private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0); - //------------------------------------------------------ // initialization - @SuppressWarnings("StaticAssignmentInConstructor") SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa, - boolean binauralEnabledDefault, - boolean transauralEnabledDefault, - boolean headTrackingEnabledDefault) { + @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault, + boolean transauralEnabledDefault, boolean headTrackingEnabledDefault) { mAudioService = mother; mASA = asa; - // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being - // constructed here is the factory for SADeviceState, thus SADeviceState and its - // private static field sHeadTrackingEnabledDefault should never be accessed directly. - SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault; - SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault; - SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault; + mDeviceBroker = deviceBroker; + + mBinauralEnabledDefault = binauralEnabledDefault; + mTransauralEnabledDefault = transauralEnabledDefault; + mHeadTrackingEnabledDefault = headTrackingEnabledDefault; } - synchronized void init(boolean effectExpected, @Nullable String settings) { + synchronized void init(boolean effectExpected) { loglogi("init effectExpected=" + effectExpected); if (!effectExpected) { loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected"); @@ -288,10 +279,11 @@ public class SpatializerHelper { } } - // When initialized from AudioService, the settings string will be non-null. - // Saved settings need to be applied after spatialization support is initialized above. - if (settings != null) { - setSADeviceSettings(settings); + // Log the saved device states that are compatible with SA + for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) { + if (isSADevice(deviceState)) { + logDeviceState(deviceState, "setSADeviceSettings"); + } } // for both transaural / binaural, we are not forcing enablement as the init() method @@ -331,7 +323,7 @@ public class SpatializerHelper { mState = STATE_UNINITIALIZED; mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - init(true, null /* settings */); + init(/*effectExpected=*/true); setSpatializerEnabledInt(featureEnabled); } @@ -372,7 +364,7 @@ public class SpatializerHelper { final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); // is media routed to a new device? - if (isWireless(currentDevice.getType())) { + if (isBluetoothDevice(currentDevice.getInternalType())) { addWirelessDeviceIfNew(currentDevice); } @@ -520,8 +512,8 @@ public class SpatializerHelper { synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>(); - for (SADeviceState deviceState : mSADevices) { - if (deviceState.mEnabled) { + for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) { + if (deviceState.isSAEnabled() && isSADevice(deviceState)) { compatList.add(deviceState.getAudioDeviceAttributes()); } } @@ -548,29 +540,48 @@ public class SpatializerHelper { return; } loglogi("addCompatibleAudioDevice: dev=" + ada); - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - SADeviceState deviceUpdated = null; // non-null on update. + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + initSAState(deviceState); + AdiDeviceState updatedDevice = null; // non-null on update. if (deviceState != null) { - if (forceEnable && !deviceState.mEnabled) { - deviceUpdated = deviceState; - deviceUpdated.mEnabled = true; + if (forceEnable && !deviceState.isSAEnabled()) { + updatedDevice = deviceState; + updatedDevice.setSAEnabled(true); } } else { // When adding, force the device type to be a canonical one. - final int canonicalDeviceType = getCanonicalDeviceType(ada.getType()); + final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), + ada.getInternalType()); if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes " + ada); return; } - deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress()); - mSADevices.add(deviceUpdated); + updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), + ada.getAddress()); + initSAState(updatedDevice); + mDeviceBroker.addDeviceStateToInventory(updatedDevice); } - if (deviceUpdated != null) { + if (updatedDevice != null) { onRoutingUpdated(); - mAudioService.persistSpatialAudioDeviceSettings(); - logDeviceState(deviceUpdated, "addCompatibleAudioDevice"); + mDeviceBroker.persistAudioDeviceSettings(); + logDeviceState(updatedDevice, "addCompatibleAudioDevice"); + } + } + + private void initSAState(AdiDeviceState device) { + if (device == null) { + return; } + + int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(), + Integer.MIN_VALUE); + device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL + ? mBinauralEnabledDefault + : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL + ? mTransauralEnabledDefault + : false); + device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault); } private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device."; @@ -580,29 +591,30 @@ public class SpatializerHelper { // // There may be different devices with the same device type (aliasing). // We always send the full device state info on each change. - private void logDeviceState(SADeviceState deviceState, String event) { + static void logDeviceState(AdiDeviceState deviceState, String event) { final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice( - deviceState.mDeviceType); + deviceState.getDeviceType()); final String deviceName = AudioSystem.getDeviceName(deviceType); new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName) - .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress) - .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false") - .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event)) - .set(MediaMetrics.Property.HAS_HEAD_TRACKER, - deviceState.mHasHeadTracker ? "true" : "false") // this may be updated later. - .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED, - deviceState.mHeadTrackerEnabled ? "true" : "false") - .record(); + .set(MediaMetrics.Property.ADDRESS, deviceState.getDeviceAddress()) + .set(MediaMetrics.Property.ENABLED, deviceState.isSAEnabled() ? "true" : "false") + .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event)) + .set(MediaMetrics.Property.HAS_HEAD_TRACKER, + deviceState.hasHeadTracker() ? "true" + : "false") // this may be updated later. + .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED, + deviceState.isHeadTrackerEnabled() ? "true" : "false") + .record(); } synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { loglogi("removeCompatibleAudioDevice: dev=" + ada); - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - if (deviceState != null && deviceState.mEnabled) { - deviceState.mEnabled = false; + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + if (deviceState != null && deviceState.isSAEnabled()) { + deviceState.setSAEnabled(false); onRoutingUpdated(); - mAudioService.persistSpatialAudioDeviceSettings(); + mDeviceBroker.persistAudioDeviceSettings(); logDeviceState(deviceState, "removeCompatibleAudioDevice"); } } @@ -611,8 +623,9 @@ public class SpatializerHelper { * Returns a possibly aliased device type which is used * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist). */ - private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) { - if (isWireless(deviceType)) return deviceType; + @AudioDeviceInfo.AudioDeviceType + private static int getCanonicalDeviceType(int deviceType, int internalDeviceType) { + if (isBluetoothDevice(internalDeviceType)) return deviceType; final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { @@ -629,18 +642,9 @@ public class SpatializerHelper { */ @GuardedBy("this") @Nullable - private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) { - final int deviceType = ada.getType(); - final boolean isWireless = isWireless(deviceType); - final int canonicalDeviceType = getCanonicalDeviceType(deviceType); - - for (SADeviceState deviceState : mSADevices) { - if (deviceState.mDeviceType == canonicalDeviceType - && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { - return deviceState; - } - } - return null; + private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) { + return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada, + getCanonicalDeviceType(ada.getType(), ada.getInternalType())); } /** @@ -662,14 +666,14 @@ public class SpatializerHelper { Log.e(TAG, "no spatialization mode found for device type:" + deviceType); return new Pair<>(false, false); } - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); if (deviceState == null) { // no matching device state? Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada); return new Pair<>(false, false); } // found the matching device state. - return new Pair<>(deviceState.mEnabled, true /* available */); + return new Pair<>(deviceState.isSAEnabled(), true /* available */); } private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) { @@ -678,16 +682,19 @@ public class SpatializerHelper { } if (findDeviceStateForAudioDeviceAttributes(ada) == null) { // wireless device types should be canonical, but we translate to be sure. - final int canonicalDeviceType = getCanonicalDeviceType((ada.getType())); + final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), + ada.getInternalType()); if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes " + ada); return; } - final SADeviceState deviceState = - new SADeviceState(canonicalDeviceType, ada.getAddress()); - mSADevices.add(deviceState); - mAudioService.persistSpatialAudioDeviceSettings(); + final AdiDeviceState deviceState = + new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), + ada.getAddress()); + initSAState(deviceState); + mDeviceBroker.addDeviceStateToInventory(deviceState); + mDeviceBroker.persistAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } } @@ -756,6 +763,12 @@ public class SpatializerHelper { return false; } + private boolean isSADevice(AdiDeviceState deviceState) { + return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(), + deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes( + deviceState.getAudioDeviceAttributes()); + } + synchronized void setFeatureEnabled(boolean enabled) { loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled); if (mFeatureEnabled == enabled) { @@ -768,7 +781,7 @@ public class SpatializerHelper { return; } if (mState == STATE_UNINITIALIZED) { - init(true, null /* settings */); + init(true); } setSpatializerEnabledInt(true); } else { @@ -1137,16 +1150,16 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled + " for " + ada); } - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); if (deviceState == null) return; - if (!deviceState.mHasHeadTracker) { + if (!deviceState.hasHeadTracker()) { Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled + " device:" + ada + " on a device without headtracker"); return; } Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); - deviceState.mHeadTrackerEnabled = enabled; - mAudioService.persistSpatialAudioDeviceSettings(); + deviceState.setHeadTrackerEnabled(enabled); + mDeviceBroker.persistAudioDeviceSettings(); logDeviceState(deviceState, "setHeadTrackerEnabled"); // check current routing to see if it affects the headtracking mode @@ -1170,8 +1183,8 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada); return false; } - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); - return deviceState != null && deviceState.mHasHeadTracker; + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + return deviceState != null && deviceState.hasHeadTracker(); } /** @@ -1184,14 +1197,14 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada); return false; } - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); if (deviceState != null) { - if (!deviceState.mHasHeadTracker) { - deviceState.mHasHeadTracker = true; - mAudioService.persistSpatialAudioDeviceSettings(); + if (!deviceState.hasHeadTracker()) { + deviceState.setHasHeadTracker(true); + mDeviceBroker.persistAudioDeviceSettings(); logDeviceState(deviceState, "setHasHeadTracker"); } - return deviceState.mHeadTrackerEnabled; + return deviceState.isHeadTrackerEnabled(); } Log.e(TAG, "setHasHeadTracker: device not found for:" + ada); return false; @@ -1202,9 +1215,9 @@ public class SpatializerHelper { Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada); return false; } - final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); + final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); return deviceState != null - && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled; + && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled(); } synchronized boolean isHeadTrackerAvailable() { @@ -1543,144 +1556,6 @@ public class SpatializerHelper { pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:" + mTransauralSupported); pw.println("\tmSpatOutput:" + mSpatOutput); - pw.println("\tdevices:"); - for (SADeviceState device : mSADevices) { - pw.println("\t\t" + device); - } - } - - /*package*/ static final class SADeviceState { - private static boolean sBinauralEnabledDefault = true; - private static boolean sTransauralEnabledDefault = true; - private static boolean sHeadTrackingEnabledDefault = false; - final @AudioDeviceInfo.AudioDeviceType int mDeviceType; - final @NonNull String mDeviceAddress; - boolean mEnabled; - boolean mHasHeadTracker = false; - boolean mHeadTrackerEnabled; - static final String SETTING_FIELD_SEPARATOR = ","; - static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; - static final String SETTING_DEVICE_SEPARATOR = "\\|"; - - /** - * Constructor - * @param deviceType - * @param address must be non-null for wireless devices - * @throws NullPointerException if a null address is passed for a wireless device - */ - SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) { - mDeviceType = deviceType; - mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : ""; - final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); - mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL - ? sBinauralEnabledDefault - : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL - ? sTransauralEnabledDefault - : false; - mHeadTrackerEnabled = sHeadTrackingEnabledDefault; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - // type check and cast - if (getClass() != obj.getClass()) { - return false; - } - final SADeviceState sads = (SADeviceState) obj; - return mDeviceType == sads.mDeviceType - && mDeviceAddress.equals(sads.mDeviceAddress) - && mEnabled == sads.mEnabled - && mHasHeadTracker == sads.mHasHeadTracker - && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker, - mHeadTrackerEnabled); - } - - @Override - public String toString() { - return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled - + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; - } - - String toPersistableString() { - return (new StringBuilder().append(mDeviceType) - .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) - .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0") - .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") - .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") - .toString()); - } - - static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) { - if (persistedString == null) { - return null; - } - if (persistedString.isEmpty()) { - return null; - } - String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); - if (fields.length != 5) { - // expecting all fields, fewer may mean corruption, ignore those settings - return null; - } - try { - final int deviceType = Integer.parseInt(fields[0]); - final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]); - deviceState.mEnabled = Integer.parseInt(fields[2]) == 1; - deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1; - deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1; - return deviceState; - } catch (NumberFormatException e) { - Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e); - return null; - } - } - - public AudioDeviceAttributes getAudioDeviceAttributes() { - return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, - mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress); - } - - } - - /*package*/ synchronized String getSADeviceSettings() { - // expected max size of each String for each SADeviceState is 25 (accounting for separator) - final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25); - for (int i = 0; i < mSADevices.size(); i++) { - settingsBuilder.append(mSADevices.get(i).toPersistableString()); - if (i != mSADevices.size() - 1) { - settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR); - } - } - return settingsBuilder.toString(); - } - - /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) { - String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings), - SADeviceState.SETTING_DEVICE_SEPARATOR); - // small list, not worth overhead of Arrays.stream(devSettings) - for (String setting : devSettings) { - SADeviceState devState = SADeviceState.fromPersistedString(setting); - // Note if the device is not compatible with spatialization mode - // or the device type is not canonical, it is ignored. - if (devState != null - && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType) - && isDeviceCompatibleWithSpatializationModes( - devState.getAudioDeviceAttributes())) { - mSADevices.add(devState); - logDeviceState(devState, "setSADeviceSettings"); - } - } } private static String spatStateString(int state) { @@ -1702,15 +1577,6 @@ public class SpatializerHelper { } } - private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) { - for (int type : WIRELESS_TYPES) { - if (type == deviceType) { - return true; - } - } - return false; - } - private int getHeadSensorHandleUpdateTracker() { int headHandle = -1; if (sRoutingDevices.isEmpty()) { @@ -1780,11 +1646,6 @@ public class SpatializerHelper { //------------------------------------------------ // for testing purposes only - - /*package*/ void clearSADevices() { - mSADevices.clear(); - } - /*package*/ synchronized void forceStateForTest(int state) { mState = state; } diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index f8f0088ac047..df16c5bcc673 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -1015,6 +1015,12 @@ public class CameraServiceProxy extends SystemService handles.add(id); } + // If the device is running in headless system user mode then allow + // User 0 to access camera. + if (UserManager.isHeadlessSystemUserMode()) { + handles.add(UserHandle.USER_SYSTEM); + } + return handles; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 4f7a2ba58570..619186157cce 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -80,8 +80,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { @VisibleForTesting static final String UNIQUE_ID_PREFIX = "virtual:"; - private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = - new ArrayMap<IBinder, VirtualDisplayDevice>(); + private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<>(); private final Handler mHandler; private final SurfaceControlDisplayFactory mSurfaceControlDisplayFactory; @@ -113,9 +112,16 @@ public class VirtualDisplayAdapter extends DisplayAdapter { public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback, IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface, int flags, VirtualDisplayConfig virtualDisplayConfig) { + IBinder appToken = callback.asBinder(); + if (mVirtualDisplayDevices.containsKey(appToken)) { + Slog.wtfStack(TAG, + "Can't create virtual display, display with same appToken already exists"); + return null; + } + String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; - IBinder appToken = callback.asBinder(); + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, virtualDisplayConfig.getRequestedRefreshRate()); final String baseUniqueId = diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 9ce6f8fec4b2..7c3ef19d95a6 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -260,7 +260,9 @@ public final class MediaProjectionManagerService extends SystemService } synchronized (mLock) { - mProjectionGrant.stop(); + if (mProjectionGrant != null) { + mProjectionGrant.stop(); + } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index 919fc712c409..c240bcbce911 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -27,6 +27,9 @@ public interface NotificationManagerInternal { NotificationChannelGroup getNotificationChannelGroup(String pkg, int uid, String channelId); void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, String tag, int id, Notification notification, int userId); + void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int userId, + boolean byForegroundService); void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid, String tag, int id, int userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7452aabaa91a..009e097ec643 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2531,7 +2531,8 @@ public class NotificationManagerService extends SystemService { } enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(), r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(), - r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn); + r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn, + false /* byForegroundService */); } catch (Exception e) { Slog.e(TAG, "Cannot un-snooze notification", e); } @@ -3526,7 +3527,8 @@ public class NotificationManagerService extends SystemService { public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int userId) throws RemoteException { enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(), - Binder.getCallingPid(), tag, id, notification, userId); + Binder.getCallingPid(), tag, id, notification, userId, + false /* byForegroundService */); } @Override @@ -6092,7 +6094,7 @@ public class NotificationManagerService extends SystemService { } if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid, summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord, - true)) { + true, false)) { return summaryRecord; } } @@ -6421,7 +6423,15 @@ public class NotificationManagerService extends SystemService { public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid, String tag, int id, Notification notification, int userId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - userId); + userId, false /* byForegroundService */); + } + + @Override + public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int userId, + boolean byForegroundService) { + enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, + userId, byForegroundService); } @Override @@ -6599,19 +6609,19 @@ public class NotificationManagerService extends SystemService { void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId) { + int incomingUserId, boolean byForegroundService) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - incomingUserId, false); + incomingUserId, false /* postSilently */, byForegroundService); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId, boolean postSilently) { + int incomingUserId, boolean postSilently, boolean byForegroundService) { PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid); boolean enqueued = false; try { enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, - notification, incomingUserId, postSilently, tracker); + notification, incomingUserId, postSilently, tracker, byForegroundService); } finally { if (!enqueued) { tracker.cancel(); @@ -6642,10 +6652,10 @@ public class NotificationManagerService extends SystemService { * @return True if we successfully processed the notification and handed off the task of * enqueueing it to a background thread; false otherwise. */ - private boolean enqueueNotificationInternal(final String pkg, final String opPkg, + private boolean enqueueNotificationInternal(final String pkg, final String opPkg, //HUI final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently, - PostNotificationTracker tracker) { + PostNotificationTracker tracker, boolean byForegroundService) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -6791,7 +6801,7 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid)); if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r, - r.getSbn().getOverrideGroupKey() != null)) { + r.getSbn().getOverrideGroupKey() != null, byForegroundService)) { return false; } @@ -7211,7 +7221,7 @@ public class NotificationManagerService extends SystemService { * Has side effects. */ boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, - NotificationRecord r, boolean isAutogroup) { + NotificationRecord r, boolean isAutogroup, boolean byForegroundService) { Notification n = r.getNotification(); final String pkg = r.getSbn().getPackageName(); final boolean isSystemNotification = @@ -7302,7 +7312,8 @@ public class NotificationManagerService extends SystemService { if (n.isStyle(Notification.CallStyle.class)) { boolean hasFullScreenIntent = n.fullScreenIntent != null; boolean requestedFullScreenIntent = (n.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0; - if (!n.isFgsOrUij() && !hasFullScreenIntent && !requestedFullScreenIntent) { + if (!n.isFgsOrUij() && !hasFullScreenIntent && !requestedFullScreenIntent + && !byForegroundService) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " CallStyle notifications must be for a foreground service or" + " user initated job or use a fullScreenIntent."); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 25ca1cb7bdaa..ac52f9f48def 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -723,6 +723,10 @@ public class UserManagerService extends IUserManager.Stub { user.unlockRealtime = SystemClock.elapsedRealtime(); } } + if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM + && UserManager.isCommunalProfileEnabled()) { + mUms.startCommunalProfile(); + } } @Override @@ -828,6 +832,26 @@ public class UserManagerService extends IUserManager.Stub { return mLocalService; } + private void startCommunalProfile() { + final int communalProfileId = getCommunalProfileIdUnchecked(); + if (communalProfileId != UserHandle.USER_NULL) { + Slogf.d(LOG_TAG, "Starting the Communal Profile"); + boolean started = false; + try { + started = ActivityManager.getService().startProfile(communalProfileId); + } catch (RemoteException e) { + // Should not happen - same process + e.rethrowAsRuntimeException(); + } + if (!started) { + Slogf.wtf(LOG_TAG, + "Failed to start communal profile userId=%d", communalProfileId); + } + } else { + Slogf.w(LOG_TAG, "Cannot start Communal Profile because there isn't one"); + } + } + /** Marks all ephemeral users as slated for deletion. **/ private void markEphemeralUsersForRemoval() { synchronized (mUsersLock) { diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java index f0bf1ea80570..d0c346a63889 100644 --- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java +++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java @@ -334,7 +334,10 @@ public class ArtStatsLogUtils { ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN), cancellationReason, durationMs, - 0); // deprecated, used to be durationIncludingSleepMs + 0, // deprecated, used to be durationIncludingSleepMs + 0, // optimizedPackagesCount + 0, // packagesDependingOnBootClasspathCount + 0); // totalPackagesCount } } } diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp new file mode 100644 index 000000000000..1da9dd770ba6 --- /dev/null +++ b/services/core/java/com/android/server/power/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "power_optimization_flags", + package: "com.android.server.power.optimization", + srcs: [ + "stats/*.aconfig", + ], +} + +java_aconfig_library { + name: "power_optimization_flags_lib", + aconfig_declarations: "power_optimization_flags", +} diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig new file mode 100644 index 000000000000..d61bebc0c82c --- /dev/null +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.server.power.optimization" + +flag { + name: "streamlined_battery_stats" + namespace: "power_optimization" + description: "Feature flag for streamlined battery stats" + bug: "285646152" +} diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2c866abe614a..8673b90c966d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.BIND_VOICE_INTERACTION; import static android.Manifest.permission.CHANGE_CONFIGURATION; +import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.DETECT_SCREEN_CAPTURE; import static android.Manifest.permission.INTERACT_ACROSS_USERS; @@ -3549,6 +3550,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void keyguardGoingAway(int flags) { + mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, "unlock keyguard"); enforceNotIsolatedCaller("keyguardGoingAway"); final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index d7667d8ce7a8..4f3ab8bbbe0a 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -349,7 +349,8 @@ class Dimmer { // Otherwise use the same duration as the animation on the WindowContainer AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); - return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION + final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); + return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) : animationAdapter.getDurationHint(); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 99cbdde306a2..70edf3a733da 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -303,8 +303,7 @@ public class DisplayRotation { mOrientationListener.setCurrentRotation(mRotation); mSettingsObserver = new SettingsObserver(uiHandler); mSettingsObserver.observe(); - if (mSupportAutoRotation && mContext.getResources().getBoolean( - R.bool.config_windowManagerHalfFoldAutoRotateOverride)) { + if (mSupportAutoRotation && isFoldable(mContext)) { mFoldController = new FoldController(); } else { mFoldController = null; @@ -314,6 +313,10 @@ public class DisplayRotation { } } + private static boolean isFoldable(Context context) { + return context.getResources().getIntArray(R.array.config_foldedDeviceStates).length > 0; + } + @VisibleForTesting @Nullable DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy( @@ -1469,11 +1472,6 @@ public class DisplayRotation { return false; } - // Do not show rotation choice when fold controller blocks rotation sensor - if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) { - return false; - } - // Don't show rotation choice if we are in tabletop or book modes. if (isTabletopAutoRotateOverrideEnabled()) return false; @@ -1781,8 +1779,11 @@ public class DisplayRotation { private SensorEventListener mHingeAngleSensorEventListener; private final Set<Integer> mTabletopRotations; private final Runnable mActivityBoundsUpdateCallback; + private final boolean mAllowHalfFoldAutoRotationOverride; FoldController() { + mAllowHalfFoldAutoRotationOverride = mContext.getResources().getBoolean( + R.bool.config_windowManagerHalfFoldAutoRotateOverride); mTabletopRotations = new ArraySet<>(); int[] tabletop_rotations = mContext.getResources().getIntArray( R.array.config_deviceTabletopRotations); @@ -1900,12 +1901,14 @@ public class DisplayRotation { } boolean overrideFrozenRotation() { - return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED; + return mAllowHalfFoldAutoRotationOverride + && mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED; } boolean shouldRevertOverriddenRotation() { // When transitioning to open. - return mDeviceState == DeviceStateController.DeviceState.OPEN + return mAllowHalfFoldAutoRotationOverride + && mDeviceState == DeviceStateController.DeviceState.OPEN && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving && mInHalfFoldTransition && mDisplayContent.getRotationReversionController().isOverrideActive( diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 9c08c74458ff..af770e27bc4a 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -56,6 +56,7 @@ import android.view.WindowManager; import android.view.animation.Animation; import android.window.ScreenCapture; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.ProtoLog; @@ -91,10 +92,12 @@ class WallpaperController { private float mLastWallpaperZoomOut = 0; private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; - private final float mMaxWallpaperScale; // Whether COMMAND_FREEZE was dispatched. private boolean mLastFrozen = false; + private float mMinWallpaperScale; + private float mMaxWallpaperScale; + // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. private WindowState mWaitingOnWallpaper; @@ -240,14 +243,16 @@ class WallpaperController { WallpaperController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; + mIsLockscreenLiveWallpaperEnabled = + SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true); + Resources resources = service.mContext.getResources(); - mMaxWallpaperScale = - resources.getFloat(com.android.internal.R.dimen.config_wallpaperMaxScale); + mMinWallpaperScale = + resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale); + mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale); mShouldOffsetWallpaperCenter = resources.getBoolean( com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay); - mIsLockscreenLiveWallpaperEnabled = - SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true); } void resetLargestDisplay(Display display) { @@ -256,6 +261,16 @@ class WallpaperController { } } + @VisibleForTesting + void setMinWallpaperScale(float minScale) { + mMinWallpaperScale = minScale; + } + + @VisibleForTesting + void setMaxWallpaperScale(float maxScale) { + mMaxWallpaperScale = maxScale; + } + @VisibleForTesting void setShouldOffsetWallpaperCenter(boolean shouldOffset) { mShouldOffsetWallpaperCenter = shouldOffset; } @@ -1010,8 +1025,8 @@ class WallpaperController { } } - private float zoomOutToScale(float zoom) { - return MathUtils.lerp(1, mMaxWallpaperScale, 1 - zoom); + private float zoomOutToScale(float zoomOut) { + return MathUtils.lerp(mMinWallpaperScale, mMaxWallpaperScale, 1 - zoomOut); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6d0fd2a51855..029f46f92dbe 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5682,6 +5682,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // TODO(b/233286785): Add sync support to wallpaper. return true; } + if (mActivityRecord != null && mViewVisibility != View.VISIBLE + && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION + && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) { + // Skip sync for invisible app windows which are not managed by activity lifecycle. + return false; + } // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its // children to finish and is immediately ready from its own diff --git a/services/java/com/android/server/CommunalProfileInitializer.java b/services/java/com/android/server/CommunalProfileInitializer.java new file mode 100644 index 000000000000..32715185469a --- /dev/null +++ b/services/java/com/android/server/CommunalProfileInitializer.java @@ -0,0 +1,86 @@ +/* + * 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; + +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.server.am.ActivityManagerService; +import com.android.server.pm.UserManagerInternal; +import com.android.server.utils.Slogf; +import com.android.server.utils.TimingsTraceAndSlog; + +/** + * Responsible for creating the communal profile at first boot, if required. + */ +public class CommunalProfileInitializer { + + private static final String TAG = CommunalProfileInitializer.class.getSimpleName(); + + private UserManagerInternal mUmi; + private final ActivityManagerService mAms; + + public CommunalProfileInitializer(ActivityManagerService ams) { + mUmi = LocalServices.getService(UserManagerInternal.class); + mAms = ams; + } + + /** + * Initialize this object and create the Communal Profile if needed. + */ + public void init(TimingsTraceAndSlog t) { + Slogf.i(TAG, "init())"); + + t.traceBegin("createCommunalProfileIfNeeded"); + createCommunalProfileIfNeeded(); + t.traceEnd(); + } + + private void createCommunalProfileIfNeeded() { + final int communalProfile = mUmi.getCommunalProfileId(); + if (communalProfile != UserHandle.USER_NULL) { + Slogf.d(TAG, "Found existing Communal Profile, userId=%d", communalProfile); + return; + } + + Slogf.d(TAG, "Creating a new Communal Profile"); + try { + // TODO: b/293860614 - Create Communal Profile string name + final UserInfo newProfile = mUmi.createUserEvenWhenDisallowed( + /* name= */ null, + UserManager.USER_TYPE_PROFILE_COMMUNAL, + /* flags= */ 0, /* disallowedPackages= */ null, /* token= */ null); + Slogf.i(TAG, "Successfully created Communal Profile, userId=%d", newProfile.id); + } catch (UserManager.CheckedUserOperationException e) { + Slogf.wtf(TAG, "Communal Profile creation failed", e); + } + } + + static void removeCommunalProfileIfPresent() { + final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + final int communalProfile = umi.getCommunalProfileId(); + if (communalProfile == UserHandle.USER_NULL) { + return; + } + Slogf.d(TAG, "Removing existing Communal Profile, userId=%d", communalProfile); + final boolean removeSucceeded = umi.removeUserEvenWhenDisallowed(communalProfile); + if (!removeSucceeded) { + Slogf.e(TAG, "Failed to remove Communal Profile, userId=%d", communalProfile); + } + } + +} diff --git a/services/java/com/android/server/HsumBootUserInitializer.java b/services/java/com/android/server/HsumBootUserInitializer.java index 00396e2c2b69..0bf8e71d9052 100644 --- a/services/java/com/android/server/HsumBootUserInitializer.java +++ b/services/java/com/android/server/HsumBootUserInitializer.java @@ -63,13 +63,11 @@ final class HsumBootUserInitializer { /** Whether this device should always have a non-removable MainUser, including at first boot. */ private final boolean mShouldAlwaysHaveMainUser; - /** Whether this device should have a communal profile created at first boot. */ - private final boolean mShouldAlwaysHaveCommunalProfile; /** Static factory method for creating a {@link HsumBootUserInitializer} instance. */ public static @Nullable HsumBootUserInitializer createInstance(ActivityManagerService am, PackageManagerService pms, ContentResolver contentResolver, - boolean shouldAlwaysHaveMainUser, boolean shouldAlwaysHaveCommunalProfile) { + boolean shouldAlwaysHaveMainUser) { if (!UserManager.isHeadlessSystemUserMode()) { return null; @@ -77,18 +75,17 @@ final class HsumBootUserInitializer { return new HsumBootUserInitializer( LocalServices.getService(UserManagerInternal.class), am, pms, contentResolver, - shouldAlwaysHaveMainUser, shouldAlwaysHaveCommunalProfile); + shouldAlwaysHaveMainUser); } private HsumBootUserInitializer(UserManagerInternal umi, ActivityManagerService am, PackageManagerService pms, ContentResolver contentResolver, - boolean shouldAlwaysHaveMainUser, boolean shouldAlwaysHaveCommunalProfile) { + boolean shouldAlwaysHaveMainUser) { mUmi = umi; mAms = am; mPms = pms; mContentResolver = contentResolver; mShouldAlwaysHaveMainUser = shouldAlwaysHaveMainUser; - mShouldAlwaysHaveCommunalProfile = shouldAlwaysHaveCommunalProfile; } /** @@ -106,11 +103,6 @@ final class HsumBootUserInitializer { createMainUserIfNeeded(); t.traceEnd(); } - if (mShouldAlwaysHaveCommunalProfile) { - t.traceBegin("createCommunalProfileIfNeeded"); - createCommunalProfileIfNeeded(); - t.traceEnd(); - } } private void createMainUserIfNeeded() { @@ -134,25 +126,6 @@ final class HsumBootUserInitializer { } } - private void createCommunalProfileIfNeeded() { - final int communalProfile = mUmi.getCommunalProfileId(); - if (communalProfile != UserHandle.USER_NULL) { - Slogf.d(TAG, "Found existing Communal Profile, userId=%d", communalProfile); - return; - } - - Slogf.d(TAG, "Creating a new Communal Profile"); - try { - final UserInfo newProfile = mUmi.createUserEvenWhenDisallowed( - /* name= */ null, // TODO: Create Communal Profile string name - UserManager.USER_TYPE_PROFILE_COMMUNAL, - /* flags= */ 0, /* disallowedPackages= */ null, /* token= */ null); - Slogf.i(TAG, "Successfully created Communal Profile, userId=%d", newProfile.id); - } catch (UserManager.CheckedUserOperationException e) { - Slogf.wtf(TAG, "Communal Profile creation failed", e); - } - } - /** * Put the device into the correct user state: unlock the system and switch to the boot user. * @@ -176,48 +149,6 @@ final class HsumBootUserInitializer { } } - /** - * Handles any final initialization once the system is already ready. - * - * <p>Should only call after {@link ActivityManagerService#systemReady} is completed. - */ - public void postSystemReady(TimingsTraceAndSlog t) { - if (mShouldAlwaysHaveCommunalProfile) { - startCommunalProfile(t); - } else { - // As a safeguard, disabling the Communal Profile configuration (or SystemProperty) will - // purposefully trigger the removal of the Communal Profile at boot time. - removeCommunalProfileIfNeeded(); - } - } - - private void startCommunalProfile(TimingsTraceAndSlog t) { - final int communalProfileId = mUmi.getCommunalProfileId(); - if (communalProfileId != UserHandle.USER_NULL) { - Slogf.d(TAG, "Starting the Communal Profile"); - t.traceBegin("startCommunalProfile-" + communalProfileId); - final boolean started = mAms.startProfile(communalProfileId); - if (!started) { - Slogf.wtf(TAG, "Failed to start communal profile userId=%d", communalProfileId); - } - t.traceEnd(); - } else { - Slogf.w(TAG, "Cannot start Communal Profile because there isn't one"); - } - } - - private void removeCommunalProfileIfNeeded() { - final int communalProfile = mUmi.getCommunalProfileId(); - if (communalProfile == UserHandle.USER_NULL) { - return; - } - Slogf.d(TAG, "Removing existing Communal Profile, userId=%d", communalProfile); - final boolean removeSucceeded = mUmi.removeUserEvenWhenDisallowed(communalProfile); - if (!removeSucceeded) { - Slogf.e(TAG, "Failed to Communal Profile, userId=%d", communalProfile); - } - } - private void observeDeviceProvisioning() { if (isDeviceProvisioned()) { return; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6a2d4dc333f6..caa434366a17 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2781,14 +2781,26 @@ public final class SystemServer implements Dumpable { final HsumBootUserInitializer hsumBootUserInitializer = HsumBootUserInitializer.createInstance( mActivityManagerService, mPackageManagerService, mContentResolver, - context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin), - UserManager.isCommunalProfileEnabled()); + context.getResources().getBoolean(R.bool.config_isMainUserPermanentAdmin)); if (hsumBootUserInitializer != null) { t.traceBegin("HsumBootUserInitializer.init"); hsumBootUserInitializer.init(t); t.traceEnd(); } + CommunalProfileInitializer communalProfileInitializer = null; + if (UserManager.isCommunalProfileEnabled()) { + t.traceBegin("CommunalProfileInitializer.init"); + communalProfileInitializer = + new CommunalProfileInitializer(mActivityManagerService); + communalProfileInitializer.init(t); + t.traceEnd(); + } else { + t.traceBegin("CommunalProfileInitializer.removeCommunalProfileIfPresent"); + CommunalProfileInitializer.removeCommunalProfileIfPresent(); + t.traceEnd(); + } + t.traceBegin("StartBootPhaseSystemServicesReady"); mSystemServiceManager.startBootPhase(t, SystemService.PHASE_SYSTEM_SERVICES_READY); t.traceEnd(); @@ -3207,12 +3219,6 @@ public final class SystemServer implements Dumpable { t.traceEnd(); }, t); - if (hsumBootUserInitializer != null) { - t.traceBegin("HsumBootUserInitializer.postSystemReady"); - hsumBootUserInitializer.postSystemReady(t); - t.traceEnd(); - } - t.traceBegin("LockSettingsThirdPartyAppsStarted"); LockSettingsInternal lockSettingsInternal = LocalServices.getService(LockSettingsInternal.class); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index c0cfa53a0a98..486ddb4cb354 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -420,14 +420,11 @@ public class MidiService extends IMidiManager.Stub { setDeviceServer(server); } - @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS, - Manifest.permission.CREATE_USERS, - Manifest.permission.MANAGE_USERS}) public Device(BluetoothDevice bluetoothDevice) { mBluetoothDevice = bluetoothDevice; mServiceInfo = null; mUid = mBluetoothServiceUid; - mUserId = mUserManager.getMainUser().getIdentifier(); + mUserId = UserHandle.getUserId(mUid); } private void setDeviceServer(IMidiDeviceServer server) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt index 6108ad298800..ce4aa4446698 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt @@ -16,6 +16,9 @@ package com.android.server.permission.access.immutable +/** + * Immutable list with index-based access. + */ sealed class IndexedList<T>( internal val list: ArrayList<T> ) : Immutable<MutableIndexedList<T>> { @@ -34,6 +37,9 @@ sealed class IndexedList<T>( override fun toString(): String = list.toString() } +/** + * Mutable list with index-based access. + */ class MutableIndexedList<T>( list: ArrayList<T> = ArrayList() ) : IndexedList<T>(list) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt index 1202c817d97b..77e71baf0ab7 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt @@ -16,6 +16,9 @@ package com.android.server.permission.access.immutable +/** + * Immutable set with index-based access, implemented using a list. + */ sealed class IndexedListSet<T>( internal val list: ArrayList<T> ) : Immutable<MutableIndexedListSet<T>> { @@ -36,6 +39,9 @@ sealed class IndexedListSet<T>( override fun toString(): String = list.toString() } +/** + * Mutable set with index-based access, implemented using a list. + */ class MutableIndexedListSet<T>( list: ArrayList<T> = ArrayList() ) : IndexedListSet<T>(list) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt index 5c75de83792f..299cc89d9a07 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap +/** + * Immutable map with index-based access. + */ sealed class IndexedMap<K, V>( internal val map: ArrayMap<K, V> ) : Immutable<MutableIndexedMap<K, V>> { @@ -42,6 +45,9 @@ sealed class IndexedMap<K, V>( override fun toString(): String = map.toString() } +/** + * Mutable map with index-based access. + */ class MutableIndexedMap<K, V>( map: ArrayMap<K, V> = ArrayMap() ) : IndexedMap<K, V>(map) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt index 8c963aae1967..ff76a4745c8b 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt @@ -18,6 +18,11 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap +/** + * Immutable map with index-based access and mutable data structure values. + * + * @see MutableReference + */ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( internal val map: ArrayMap<K, MutableReference<I, M>> ) : Immutable<MutableIndexedReferenceMap<K, I, M>> { @@ -42,6 +47,11 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( override fun toString(): String = map.toString() } +/** + * Mutable map with index-based access and mutable data structure values. + * + * @see MutableReference + */ class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( map: ArrayMap<K, MutableReference<I, M>> = ArrayMap() ) : IndexedReferenceMap<K, I, M>(map) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt index 986861624e4b..547e56cef62a 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArraySet +/** + * Immutable set with index-based access. + */ sealed class IndexedSet<T>( internal val set: ArraySet<T> ) : Immutable<MutableIndexedSet<T>> { @@ -37,6 +40,9 @@ sealed class IndexedSet<T>( override fun toString(): String = set.toString() } +/** + * Mutable set with index-based access. + */ class MutableIndexedSet<T>( set: ArraySet<T> = ArraySet() ) : IndexedSet<T>(set) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt index b7d8b4ccddd0..7ed29e8813ac 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.SparseArray +/** + * Immutable map with index-based access and [Int] keys. + */ sealed class IntMap<T>( internal val array: SparseArray<T> ) : Immutable<MutableIntMap<T>> { @@ -41,6 +44,9 @@ sealed class IntMap<T>( override fun toString(): String = array.toString() } +/** + * Mutable map with index-based access and [Int] keys. + */ class MutableIntMap<T>( array: SparseArray<T> = SparseArray() ) : IntMap<T>(array) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt index 22fa8f2a7c7e..160b2279a0ba 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt @@ -18,6 +18,11 @@ package com.android.server.permission.access.immutable import android.util.SparseArray +/** + * Immutable map with index-based access, [Int] keys and mutable data structure values. + * + * @see MutableReference + */ sealed class IntReferenceMap<I : Immutable<M>, M : I>( internal val array: SparseArray<MutableReference<I, M>> ) : Immutable<MutableIntReferenceMap<I, M>> { @@ -42,6 +47,11 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( override fun toString(): String = array.toString() } +/** + * Mutable map with index-based access, [Int] keys and mutable data structure values. + * + * @see MutableReference + */ class MutableIntReferenceMap<I : Immutable<M>, M : I>( array: SparseArray<MutableReference<I, M>> = SparseArray() ) : IntReferenceMap<I, M>(array) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt index 9da36717db7e..21f2af20c3a9 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt @@ -18,6 +18,9 @@ package com.android.server.permission.access.immutable import android.util.SparseBooleanArray +/** + * Immutable set with index-based access and [Int] elements. + */ sealed class IntSet( internal val array: SparseBooleanArray ) : Immutable<MutableIntSet> { @@ -37,6 +40,9 @@ sealed class IntSet( override fun toString(): String = array.toString() } +/** + * Mutable set with index-based access and [Int] elements. + */ class MutableIntSet( array: SparseBooleanArray = SparseBooleanArray() ) : IntSet(array) { diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt index e39a3bbc8942..171cfeb4379d 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt @@ -16,14 +16,39 @@ package com.android.server.permission.access.immutable +/** + * Wrapper class for reference to a mutable data structure instance. + * + * This class encapsulates the logic to mutate/copy a mutable data structure instance and update the + * reference to the new mutated instance. It also remembers the mutated instance so that it can be + * reused during further mutations. + * + * Instances of this class should be kept private within a data structure, with the [get] method + * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate] + * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the + * data structure is mutated/copied, a new instance of this class should be obtained with + * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents + * further modifications to a data structure accessed with its immutable interface. + * + * @see MutableIndexedReferenceMap + * @see MutableIntReferenceMap + */ class MutableReference<I : Immutable<M>, M : I> private constructor( private var immutable: I, private var mutable: M? ) { constructor(mutable: M) : this(mutable, mutable) + /** + * Return an immutable reference to the wrapped mutable data structure. + */ fun get(): I = immutable + /** + * Make the wrapped mutable data structure mutable, by either calling [Immutable.toMutable] and + * replacing the wrapped reference with its result, or reusing the existing reference if it's + * already mutable. + */ fun mutate(): M { mutable?.let { return it } return immutable.toMutable().also { @@ -32,6 +57,10 @@ class MutableReference<I : Immutable<M>, M : I> private constructor( } } + /** + * Create a new [MutableReference] instance with the wrapped mutable data structure being + * immutable-only again. + */ fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null) override fun equals(other: Any?): Boolean { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index d16c9c59bb1b..bf2311761891 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -248,6 +248,8 @@ public class DisplayManagerServiceTest { @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal; @Mock IVirtualDisplayCallback.Stub mMockAppToken; @Mock IVirtualDisplayCallback.Stub mMockAppToken2; + + @Mock IVirtualDisplayCallback.Stub mMockAppToken3; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock LightsManager mMockLightsManager; @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter; @@ -838,6 +840,7 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); @@ -851,7 +854,7 @@ public class DisplayManagerServiceTest { int displayId1 = localService.createVirtualDisplay( builder1.build(), - mMockAppToken /* callback */, + mMockAppToken2 /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); @@ -893,6 +896,7 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); @@ -927,7 +931,7 @@ public class DisplayManagerServiceTest { int displayId2 = localService.createVirtualDisplay( builder2.build(), - mMockAppToken /* callback */, + mMockAppToken2 /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); @@ -950,6 +954,8 @@ public class DisplayManagerServiceTest { registerDefaultDisplays(displayManager); when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2); + when(mMockAppToken3.asBinder()).thenReturn(mMockAppToken3); IVirtualDevice virtualDevice = mock(IVirtualDevice.class); when(virtualDevice.getDeviceId()).thenReturn(1); @@ -999,7 +1005,7 @@ public class DisplayManagerServiceTest { int ownDisplayGroupDisplayId = localService.createVirtualDisplay( ownDisplayGroupConfig, - mMockAppToken /* callback */, + mMockAppToken2 /* callback */, virtualDevice /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); @@ -1024,7 +1030,7 @@ public class DisplayManagerServiceTest { int defaultDisplayGroupDisplayId = localService.createVirtualDisplay( defaultDisplayGroupConfig, - mMockAppToken /* callback */, + mMockAppToken3 /* callback */, null /* virtualDeviceToken */, mock(DisplayWindowPolicyController.class), PACKAGE_NAME); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index e58ec450dd61..01e49f2e207d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -59,6 +59,7 @@ import android.view.Display; import android.view.DisplayInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -303,6 +304,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_BothDpcsSupportNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -356,6 +358,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -388,6 +391,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -418,6 +422,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -450,6 +455,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowers_AutomaticBrightness() { Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, @@ -521,6 +527,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() { DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); @@ -612,6 +619,7 @@ public final class DisplayPowerControllerTest { } @Test + @FlakyTest(bugId = 294107062) public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() { DisplayPowerControllerHolder followerHolder = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID); diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java new file mode 100644 index 000000000000..8bbacc494efd --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.display.VirtualDisplayConfig; +import android.os.IBinder; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VirtualDisplayAdapterTest { + + @Mock + Context mContextMock; + + @Mock + VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory; + + @Mock + DisplayAdapter.Listener mMockListener; + + @Mock + IVirtualDisplayCallback mMockCallback; + + @Mock + IBinder mMockBinder; + + private TestHandler mHandler; + + private VirtualDisplayAdapter mVirtualDisplayAdapter; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHandler = new TestHandler(null); + mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(), + mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory); + + when(mMockCallback.asBinder()).thenReturn(mMockBinder); + } + + @Test + public void testCreatesVirtualDisplay() { + VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + + DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + /* surface= */ null, /* flags= */ 0, config); + + assertNotNull(result); + } + + @Test + public void testDoesNotCreateVirtualDisplayForSameCallback() { + VirtualDisplayConfig config1 = new VirtualDisplayConfig.Builder("test", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + VirtualDisplayConfig config2 = new VirtualDisplayConfig.Builder("test2", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, /* projection= */ null, + /* ownerUid= */ 10, /* packageName= */ "testpackage", /* surface= */ null, + /* flags= */ 0, config1); + + DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + /* surface= */ null, /* flags= */ 0, config2); + + assertNull(result); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 1f4563fb2682..976e74083849 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -105,6 +105,7 @@ import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.ActivityTaskManagerService; import com.android.server.wm.WindowProcessController; +import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -206,8 +207,10 @@ public class MockingOomAdjusterTests { setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object()); doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr) .enqueueProcessChangeItemLocked(anyInt(), anyInt()); - sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, - new ActiveUids(sService, false)); + sService.mOomAdjuster = sService.mConstants.ENABLE_NEW_OOMADJ + ? new OomAdjusterModernImpl(sService, sService.mProcessList, + new ActiveUids(sService, false)) + : new OomAdjuster(sService, sService.mProcessList, new ActiveUids(sService, false)); sService.mOomAdjuster.mAdjSeq = 10000; sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE); if (sService.mConstants.USE_TIERED_CACHED_ADJ) { @@ -220,6 +223,11 @@ public class MockingOomAdjusterTests { LocalServices.removeServiceForTest(PackageManagerInternal.class); } + @After + public void tearDown() { + sService.mOomAdjuster.resetInternal(); + } + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { try { Field field = clazz.getDeclaredField(fieldName); @@ -249,6 +257,9 @@ public class MockingOomAdjusterTests { ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP(); lru.clear(); Collections.addAll(lru, apps); + for (ProcessRecord app : apps) { + sService.mOomAdjuster.onProcessBeginLocked(app); + } } /** @@ -259,6 +270,7 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") private void updateOomAdj(ProcessRecord... apps) { if (apps.length == 1) { + sService.mOomAdjuster.onProcessBeginLocked(apps[0]); sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE); } else { setProcessesToLru(apps); @@ -600,10 +612,13 @@ public class MockingOomAdjusterTests { s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } // Out of grace period but valid binding allows the adjustment. @@ -620,10 +635,13 @@ public class MockingOomAdjusterTests { s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } // Out of grace period and no valid binding so no adjustment. @@ -641,10 +659,13 @@ public class MockingOomAdjusterTests { nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); + sService.mOomAdjuster.onProcessBeginLocked(system); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } } @@ -657,11 +678,12 @@ public class MockingOomAdjusterTests { MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true)); system.mState.setMaxAdj(PERSISTENT_PROC_ADJ); system.mState.setHasTopUi(true); + sService.mOomAdjuster.onProcessBeginLocked(system); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(system, app); assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT); @@ -850,6 +872,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + client.mServices.setTreatLikeActivity(true); bindService(app, client, null, Context.BIND_WAIVE_PRIORITY | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1006,7 +1029,7 @@ public class MockingOomAdjusterTests { bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState()); assertNoBfsl(app); @@ -1132,7 +1155,7 @@ public class MockingOomAdjusterTests { assertNoBfsl(app); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj()); assertBfsl(app); @@ -1148,7 +1171,7 @@ public class MockingOomAdjusterTests { bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); client.mState.setRunningRemoteAnimation(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - updateOomAdj(app); + updateOomAdj(client, app); assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj()); } @@ -1199,6 +1222,8 @@ public class MockingOomAdjusterTests { updateOomAdj(client, app); assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1217,6 +1242,8 @@ public class MockingOomAdjusterTests { doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1229,9 +1256,11 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(client, app); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } { @@ -1246,10 +1275,12 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(client, app); doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); + + sService.mOomAdjuster.resetInternal(); } } @@ -1849,7 +1880,7 @@ public class MockingOomAdjusterTests { bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); - updateOomAdj(app1, app2); + updateOomAdj(client1, client2, app1, app2); assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP); @@ -1899,6 +1930,8 @@ public class MockingOomAdjusterTests { s1.getConnections().clear(); s2.getConnections().clear(); + client1.mServices.removeAllConnections(); + client2.mServices.removeAllConnections(); client1.mState.setMaxAdj(UNKNOWN_ADJ); client2.mState.setMaxAdj(UNKNOWN_ADJ); client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); @@ -1909,7 +1942,7 @@ public class MockingOomAdjusterTests { bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); - updateOomAdj(app1, app2); + updateOomAdj(client1, client2, app1, app2); // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE. assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, @@ -1922,7 +1955,7 @@ public class MockingOomAdjusterTests { doReturn(client2).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE); + updateOomAdj(client2, app2); assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); } @@ -1977,6 +2010,7 @@ public class MockingOomAdjusterTests { app.setPendingFinishAttach(true); app.mState.setHasForegroundActivities(false); + sService.mOomAdjuster.onProcessBeginLocked(app); sService.mOomAdjuster.setAttachingProcessStatesLSP(app); updateOomAdj(app); @@ -1991,7 +2025,9 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); app.setPendingFinishAttach(true); app.mState.setHasForegroundActivities(true); + doReturn(app).when(sService).getTopApp(); + sService.mOomAdjuster.onProcessBeginLocked(app); sService.mOomAdjuster.setAttachingProcessStatesLSP(app); updateOomAdj(app); @@ -2088,7 +2124,7 @@ public class MockingOomAdjusterTests { anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(sService.mServices) .scheduleServiceTimeoutLocked(any(ProcessRecord.class)); - sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE); + updateOomAdj(client1, client2, app1, app2, app3); assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState()); assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState()); @@ -2426,6 +2462,8 @@ public class MockingOomAdjusterTests { lru.clear(); lru.add(app2); lru.add(app); + sService.mOomAdjuster.onProcessBeginLocked(app2); + sService.mOomAdjuster.onProcessBeginLocked(app); final ComponentName cn = ComponentName.unflattenFromString( MOCKAPP_PACKAGENAME + "/.TestService"); @@ -2528,7 +2566,7 @@ public class MockingOomAdjusterTests { doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(app).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(app); assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index 31599eed539d..aba24fbd55b7 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -29,13 +29,14 @@ import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; import android.util.Log; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.After; @@ -54,7 +55,6 @@ public class AudioDeviceBrokerTest { private static final String TAG = "AudioDeviceBrokerTest"; private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100; - private Context mContext; // the actual class under test private AudioDeviceBroker mAudioDeviceBroker; @@ -67,13 +67,13 @@ public class AudioDeviceBrokerTest { @Before public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); mMockAudioService = mock(AudioService.class); mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); mSpySystemServer = spy(new NoOpSystemServerAdapter()); - mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory, + mAudioDeviceBroker = new AudioDeviceBroker(context, mMockAudioService, mSpyDevInventory, mSpySystemServer, mSpyAudioSystem); mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); @@ -197,6 +197,37 @@ public class AudioDeviceBrokerTest { any(Intent.class)); } + /** + * Test that constructing an AdiDeviceState instance requires a non-null address for a + * wireless type, but can take null for a non-wireless type; + * @throws Exception + */ + @Test + public void testAdiDeviceStateNullAddressCtor() throws Exception { + try { + new AdiDeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + AudioManager.DEVICE_OUT_SPEAKER, null); + new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null); + Assert.fail(); + } catch (NullPointerException e) { } + } + + @Test + public void testAdiDeviceStateStringSerialization() throws Exception { + Log.i(TAG, "starting testAdiDeviceStateStringSerialization"); + final AdiDeviceState devState = new AdiDeviceState( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioManager.DEVICE_OUT_SPEAKER, "bla"); + devState.setHasHeadTracker(false); + devState.setHeadTrackerEnabled(false); + devState.setSAEnabled(true); + final String persistString = devState.toPersistableString(); + final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString); + Log.i(TAG, "original:" + devState); + Log.i(TAG, "result :" + result); + Assert.assertEquals(devState, result); + } + private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection, boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception { when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC)) diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java index 3ad24de4cdca..ad09ef0ccdc1 100644 --- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java @@ -15,8 +15,6 @@ */ package com.android.server.audio; -import com.android.server.audio.SpatializerHelper.SADeviceState; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doNothing; @@ -26,12 +24,12 @@ import static org.mockito.Mockito.when; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; -import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioSystem; import android.util.Log; import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.Assert; @@ -55,72 +53,25 @@ public class SpatializerHelperTest { @Mock private AudioService mMockAudioService; @Spy private AudioSystemAdapter mSpyAudioSystem; - @Mock private AudioSystemAdapter mMockAudioSystem; + @Spy private AudioDeviceBroker mSpyDeviceBroker; @Before public void setUp() throws Exception { mMockAudioService = mock(AudioService.class); - } - - /** - * Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy - * AudioSystemAdapter - * @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use - * the mock adapter, mMockAudioSystem. - */ - private void setUpSpatHelper(boolean useSpyAudioSystem) { - final AudioSystemAdapter asAdapter; - if (useSpyAudioSystem) { - mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); - asAdapter = mSpyAudioSystem; - mMockAudioSystem = null; - } else { - mSpyAudioSystem = null; - mMockAudioSystem = mock(NoOpAudioSystemAdapter.class); - asAdapter = mMockAudioSystem; - } - mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter, - true /*binauralEnabledDefault*/, - true /*transauralEnabledDefault*/, - false /*headTrackingEnabledDefault*/); - - } - /** - * Test that constructing an SADeviceState instance requires a non-null address for a - * wireless type, but can take null for a non-wireless type; - * @throws Exception - */ - @Test - public void testSADeviceStateNullAddressCtor() throws Exception { - setUpSpatHelper(true /*useSpyAudioSystem*/); - try { - SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null); - devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null); - Assert.fail(); - } catch (NullPointerException e) { } - } - - @Test - public void testSADeviceStateStringSerialization() throws Exception { - Log.i(TAG, "starting testSADeviceStateStringSerialization"); - setUpSpatHelper(true /*useSpyAudioSystem*/); - final SADeviceState devState = new SADeviceState( - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla"); - devState.mHasHeadTracker = false; - devState.mHeadTrackerEnabled = false; - devState.mEnabled = true; - final String persistString = devState.toPersistableString(); - final SADeviceState result = SADeviceState.fromPersistedString(persistString); - Log.i(TAG, "original:" + devState); - Log.i(TAG, "result :" + result); - Assert.assertEquals(devState, result); + mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); + mSpyDeviceBroker = spy( + new AudioDeviceBroker( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + mMockAudioService, mSpyAudioSystem)); + mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem, + mSpyDeviceBroker, /*binauralEnabledDefault=*/true, /*transauralEnabledDefault=*/ + true, /*headTrackingEnabledDefault*/false); } @Test - public void testSADeviceSettings() throws Exception { + public void testAdiDeviceStateSettings() throws Exception { Log.i(TAG, "starting testSADeviceSettings"); - setUpSpatHelper(true /*useSpyAudioSystem*/); final AudioDeviceAttributes dev1 = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""); final AudioDeviceAttributes dev2 = @@ -128,7 +79,7 @@ public class SpatializerHelperTest { final AudioDeviceAttributes dev3 = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop"); - doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings(); + doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings(); mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/); // test with single device @@ -163,11 +114,11 @@ public class SpatializerHelperTest { * the original one. */ private void checkAddSettings() throws Exception { - String settings = mSpatHelper.getSADeviceSettings(); + String settings = mSpyDeviceBroker.getDeviceSettings(); Log.i(TAG, "device settings: " + settings); - mSpatHelper.clearSADevices(); - mSpatHelper.setSADeviceSettings(settings); - String settingsRestored = mSpatHelper.getSADeviceSettings(); + mSpyDeviceBroker.clearDeviceInventory(); + mSpyDeviceBroker.setDeviceSettings(settings); + String settingsRestored = mSpyDeviceBroker.getDeviceSettings(); Log.i(TAG, "device settingsRestored: " + settingsRestored); Assert.assertEquals(settings, settingsRestored); } @@ -179,7 +130,6 @@ public class SpatializerHelperTest { @Test public void testNoRoutingCanBeSpatialized() throws Exception { Log.i(TAG, "Starting testNoRoutingCanBeSpatialized"); - setUpSpatHelper(false /*useSpyAudioSystem*/); mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE); final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0); @@ -191,12 +141,12 @@ public class SpatializerHelperTest { .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build(); - when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean())) + when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean())) .thenReturn(emptyList); Assert.assertFalse("can be spatialized on empty routing", mSpatHelper.canBeSpatialized(media, spatialFormat)); - when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean())) + when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean())) .thenReturn(listWithNull); Assert.assertFalse("can be spatialized on null routing", mSpatHelper.canBeSpatialized(media, spatialFormat)); diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS new file mode 100644 index 000000000000..008a53f6941f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS @@ -0,0 +1 @@ +include /services/companion/java/com/android/server/companion/OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java new file mode 100644 index 000000000000..01159b1f901c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java @@ -0,0 +1,128 @@ +/* + * 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.companion.utils; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManagerInternal; +import android.content.pm.Signature; +import android.content.pm.SigningDetails; +import android.content.res.Resources; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.companion.PackageUtils; +import com.android.server.pm.pkg.AndroidPackage; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +public class PackageUtilsTest { + private static final String[] ALLOWED_PACKAGE_NAMES = new String[]{ + "allowed_app", + }; + private static final Signature[] ALLOWED_PACKAGE_SIGNATURES = new Signature[]{ + new Signature("001122"), + }; + private static final String[] DISALLOWED_PACKAGE_NAMES = new String[]{ + "disallowed_app", + }; + private static final Signature[] DISALLOWED_PACKAGE_SIGNATURES = new Signature[]{ + new Signature("778899"), + }; + + @Test + public void isAllowlisted_true() { + Context context = spy( + new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + final Resources res = spy(context.getResources()); + doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray( + com.android.internal.R.array.config_companionDevicePackages); + doReturn(android.util.PackageUtils.computeSignaturesSha256Digests( + ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray( + com.android.internal.R.array.config_companionDeviceCerts); + doReturn(res).when(context).getResources(); + PackageManagerInternal pm = mock(PackageManagerInternal.class); + AndroidPackage ap = mock(AndroidPackage.class); + SigningDetails sd = new SigningDetails( + ALLOWED_PACKAGE_SIGNATURES, + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, + null, + null); + doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]); + doReturn(sd).when(ap).getSigningDetails(); + assertTrue(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0])); + } + + @Test + public void isAllowlisted_package_disallowed() { + Context context = spy( + new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + final Resources res = spy(context.getResources()); + doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray( + com.android.internal.R.array.config_companionDevicePackages); + doReturn(android.util.PackageUtils.computeSignaturesSha256Digests( + ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray( + com.android.internal.R.array.config_companionDeviceCerts); + doReturn(res).when(context).getResources(); + PackageManagerInternal pm = mock(PackageManagerInternal.class); + AndroidPackage ap = mock(AndroidPackage.class); + SigningDetails sd = new SigningDetails( + ALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, + null, + null); + doReturn(ap).when(pm).getPackage(DISALLOWED_PACKAGE_NAMES[0]); + doReturn(sd).when(ap).getSigningDetails(); + assertFalse(PackageUtils.isPackageAllowlisted(context, pm, DISALLOWED_PACKAGE_NAMES[0])); + } + + @Test + public void isAllowlisted_signature_mismatch() { + Context context = spy( + new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + final Resources res = spy(context.getResources()); + doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray( + com.android.internal.R.array.config_companionDevicePackages); + doReturn(android.util.PackageUtils.computeSignaturesSha256Digests( + ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray( + com.android.internal.R.array.config_companionDeviceCerts); + doReturn(res).when(context).getResources(); + PackageManagerInternal pm = mock(PackageManagerInternal.class); + AndroidPackage ap = mock(AndroidPackage.class); + SigningDetails sd = new SigningDetails( + DISALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature + SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, + null, + null); + doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]); + doReturn(sd).when(ap).getSigningDetails(); + assertFalse(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0])); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt index 9f855c5ca83f..f834cb24f245 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -99,23 +99,45 @@ class AndroidPackageParsingValidationTest { @Test fun parseApplicationTag() { val tag = "application" - validateTagAttr(tag, "backupAgent", - R.styleable.AndroidManifestApplication_backupAgent, 1024) - validateTagAttrComponentName(tag, "backupAgent", - R.styleable.AndroidManifestApplication_backupAgent) - validateTagAttr(tag, "manageSpaceActivity", - R.styleable.AndroidManifestApplication_manageSpaceActivity, 1024) + validateTagAttr( + tag, + "backupAgent", + R.styleable.AndroidManifestApplication_backupAgent, + 1024 + ) + validateTagAttrComponentName( + tag, + "backupAgent", + R.styleable.AndroidManifestApplication_backupAgent + ) + validateTagAttr( + tag, + "manageSpaceActivity", + R.styleable.AndroidManifestApplication_manageSpaceActivity, + 1024 + ) validateTagAttr(tag, "name", R.styleable.AndroidManifestApplication_name, 1024) - validateTagAttrComponentName(tag, "name", - R.styleable.AndroidManifestApplication_name) + validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestApplication_name) validateTagAttr(tag, "permission", R.styleable.AndroidManifestApplication_permission, 1024) validateTagAttr(tag, "process", R.styleable.AndroidManifestApplication_process, 1024) - validateTagAttr(tag, "requiredAccountType", - R.styleable.AndroidManifestApplication_requiredAccountType, 1024) - validateTagAttr(tag, "restrictedAccountType", - R.styleable.AndroidManifestApplication_restrictedAccountType, 1024) - validateTagAttr(tag, "taskAffinity", - R.styleable.AndroidManifestApplication_taskAffinity, 1024) + validateTagAttr( + tag, + "requiredAccountType", + R.styleable.AndroidManifestApplication_requiredAccountType, + 1024 + ) + validateTagAttr( + tag, + "restrictedAccountType", + R.styleable.AndroidManifestApplication_restrictedAccountType, + 1024 + ) + validateTagAttr( + tag, + "taskAffinity", + R.styleable.AndroidManifestApplication_taskAffinity, + 1024 + ) validateTagCount("profileable", 100, tag) validateTagCount("uses-native-library", 100, tag) validateTagCount("receiver", 1000, tag) @@ -159,12 +181,23 @@ class AndroidPackageParsingValidationTest { fun parseActivityAliasTag() { val tag = "activity-alias" validateTagAttr(tag, "name", R.styleable.AndroidManifestActivityAlias_name, 1024) - validateTagAttr(tag, "permission", - R.styleable.AndroidManifestActivityAlias_permission, 1024) - validateTagAttr(tag, "targetActivity", - R.styleable.AndroidManifestActivityAlias_targetActivity, 1024) - validateTagAttrComponentName(tag, "targetActivity", - R.styleable.AndroidManifestActivityAlias_targetActivity) + validateTagAttr( + tag, + "permission", + R.styleable.AndroidManifestActivityAlias_permission, + 1024 + ) + validateTagAttr( + tag, + "targetActivity", + R.styleable.AndroidManifestActivityAlias_targetActivity, + 1024 + ) + validateTagAttrComponentName( + tag, + "targetActivity", + R.styleable.AndroidManifestActivityAlias_targetActivity + ) validateTagCount("meta-data", 1000, tag) validateTagCount("intent-filter", 20000, tag) } @@ -173,7 +206,6 @@ class AndroidPackageParsingValidationTest { fun parseUsesLibraryTag() { val tag = "uses-library" validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesLibrary_name, 1024) - validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestUsesLibrary_name) } @Test @@ -181,10 +213,17 @@ class AndroidPackageParsingValidationTest { val tag = "activity" validateTagAttr(tag, "name", R.styleable.AndroidManifestActivity_name, 1024) validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestActivity_name) - validateTagAttr(tag, "parentActivityName", - R.styleable.AndroidManifestActivity_parentActivityName, 1024) - validateTagAttrComponentName(tag, "parentActivityName", - R.styleable.AndroidManifestActivity_parentActivityName) + validateTagAttr( + tag, + "parentActivityName", + R.styleable.AndroidManifestActivity_parentActivityName, + 1024 + ) + validateTagAttrComponentName( + tag, + "parentActivityName", + R.styleable.AndroidManifestActivity_parentActivityName + ) validateTagAttr(tag, "permission", R.styleable.AndroidManifestActivity_permission, 1024) validateTagAttr(tag, "process", R.styleable.AndroidManifestActivity_process, 1024) validateTagAttr(tag, "taskAffinity", R.styleable.AndroidManifestActivity_taskAffinity, 1024) @@ -197,26 +236,49 @@ class AndroidPackageParsingValidationTest { fun parseOverlayTag() { val tag = "overlay" validateTagAttr(tag, "category", R.styleable.AndroidManifestResourceOverlay_category, 1024) - validateTagAttr(tag, "requiredSystemPropertyName", - R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, 1024) - validateTagAttr(tag, "requiredSystemPropertyValue", - R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, PROP_VALUE_MAX) - validateTagAttr(tag, "targetPackage", - R.styleable.AndroidManifestResourceOverlay_targetPackage, 256) - validateTagAttr(tag, "targetName", - R.styleable.AndroidManifestResourceOverlay_targetName, 1024) + validateTagAttr( + tag, + "requiredSystemPropertyName", + R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, + 1024 + ) + validateTagAttr( + tag, + "requiredSystemPropertyValue", + R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, + PROP_VALUE_MAX + ) + validateTagAttr( + tag, + "targetPackage", + R.styleable.AndroidManifestResourceOverlay_targetPackage, + 256 + ) + validateTagAttr( + tag, + "targetName", + R.styleable.AndroidManifestResourceOverlay_targetName, + 1024 + ) } @Test fun parseInstrumentationTag() { val tag = "instrumentation" validateTagAttr(tag, "name", R.styleable.AndroidManifestInstrumentation_name, 1024) - validateTagAttrComponentName(tag, "name", - R.styleable.AndroidManifestInstrumentation_name) - validateTagAttr(tag, "targetPackage", - R.styleable.AndroidManifestInstrumentation_targetPackage, 256) - validateTagAttr(tag, "targetProcesses", - R.styleable.AndroidManifestInstrumentation_targetProcesses, 1024) + validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestInstrumentation_name) + validateTagAttr( + tag, + "targetPackage", + R.styleable.AndroidManifestInstrumentation_targetPackage, + 256 + ) + validateTagAttr( + tag, + "targetProcesses", + R.styleable.AndroidManifestInstrumentation_targetProcesses, + 1024 + ) } @Test @@ -278,10 +340,18 @@ class AndroidPackageParsingValidationTest { validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestProvider_name) validateTagAttr(tag, "permission", R.styleable.AndroidManifestProvider_permission, 1024) validateTagAttr(tag, "process", R.styleable.AndroidManifestProvider_process, 1024) - validateTagAttr(tag, "readPermission", - R.styleable.AndroidManifestProvider_readPermission, 1024) - validateTagAttr(tag, "writePermission", - R.styleable.AndroidManifestProvider_writePermission, 1024) + validateTagAttr( + tag, + "readPermission", + R.styleable.AndroidManifestProvider_readPermission, + 1024 + ) + validateTagAttr( + tag, + "writePermission", + R.styleable.AndroidManifestProvider_writePermission, + 1024 + ) validateTagCount("grant-uri-permission", 100, tag) validateTagCount("path-permission", 100, tag) validateTagCount("meta-data", 1000, tag) @@ -292,26 +362,54 @@ class AndroidPackageParsingValidationTest { fun parseGrantUriPermissionTag() { val tag = "grant-uri-permission" validateTagAttr(tag, "path", R.styleable.AndroidManifestGrantUriPermission_path, 4000) - validateTagAttr(tag, "pathPrefix", - R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 4000) - validateTagAttr(tag, "pathPattern", - R.styleable.AndroidManifestGrantUriPermission_pathPattern, 4000) + validateTagAttr( + tag, + "pathPrefix", + R.styleable.AndroidManifestGrantUriPermission_pathPrefix, + 4000 + ) + validateTagAttr( + tag, + "pathPattern", + R.styleable.AndroidManifestGrantUriPermission_pathPattern, + 4000 + ) } @Test fun parsePathPermissionTag() { val tag = "path-permission" validateTagAttr(tag, "path", R.styleable.AndroidManifestPathPermission_path, 4000) - validateTagAttr(tag, "pathPrefix", - R.styleable.AndroidManifestPathPermission_pathPrefix, 4000) - validateTagAttr(tag, "pathPattern", - R.styleable.AndroidManifestPathPermission_pathPattern, 4000) - validateTagAttr(tag, "permission", - R.styleable.AndroidManifestPathPermission_permission, 1024) - validateTagAttr(tag, "readPermission", - R.styleable.AndroidManifestPathPermission_readPermission, 1024) - validateTagAttr(tag, "writePermission", - R.styleable.AndroidManifestPathPermission_writePermission, 1024) + validateTagAttr( + tag, + "pathPrefix", + R.styleable.AndroidManifestPathPermission_pathPrefix, + 4000 + ) + validateTagAttr( + tag, + "pathPattern", + R.styleable.AndroidManifestPathPermission_pathPattern, + 4000 + ) + validateTagAttr( + tag, + "permission", + R.styleable.AndroidManifestPathPermission_permission, + 1024 + ) + validateTagAttr( + tag, + "readPermission", + R.styleable.AndroidManifestPathPermission_readPermission, + 1024 + ) + validateTagAttr( + tag, + "writePermission", + R.styleable.AndroidManifestPathPermission_writePermission, + 1024 + ) } @Test @@ -350,8 +448,12 @@ class AndroidPackageParsingValidationTest { validateTagAttr(tag, "pathPattern", R.styleable.AndroidManifestData_pathPattern, 4000) validateTagAttr(tag, "pathPrefix", R.styleable.AndroidManifestData_pathPrefix, 4000) validateTagAttr(tag, "pathSuffix", R.styleable.AndroidManifestData_pathSuffix, 4000) - validateTagAttr(tag, "pathAdvancedPattern", - R.styleable.AndroidManifestData_pathAdvancedPattern, 4000) + validateTagAttr( + tag, + "pathAdvancedPattern", + R.styleable.AndroidManifestData_pathAdvancedPattern, + 4000 + ) validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512) } @@ -365,8 +467,12 @@ class AndroidPackageParsingValidationTest { fun parsePermissionTag() { val tag = "permission" validateTagAttr(tag, "name", R.styleable.AndroidManifestPermission_name, 1024) - validateTagAttr(tag, "permissionGroup", - R.styleable.AndroidManifestPermission_permissionGroup, 256) + validateTagAttr( + tag, + "permissionGroup", + R.styleable.AndroidManifestPermission_permissionGroup, + 256 + ) } @Test @@ -386,14 +492,18 @@ class AndroidPackageParsingValidationTest { try { validator.validateStrAttr(pullParser, attr, name) } catch (e: SecurityException) { - fail("Failed to parse attribute $attr in <$tag> as valid Java class name:" + - " ${e.message}") + fail( + "Failed to parse attribute $attr in <$tag> as valid Java class name:" + + " ${e.message}" + ) } try { validator.validateResStrAttr(pullParser, index, name) } catch (e: SecurityException) { - fail("Failed to parse attribute $attr in <$tag> as valid Java class name:" + - " ${e.message}") + fail( + "Failed to parse attribute $attr in <$tag> as valid Java class name:" + + " ${e.message}" + ) } } @@ -404,13 +514,17 @@ class AndroidPackageParsingValidationTest { val validator = Validator() pullParser.nextTag() validator.validate(pullParser) - val e1 = assertThrows("$name is not valid Java class name", - SecurityException::class.java) { + val e1 = assertThrows( + "$name is not valid Java class name", + SecurityException::class.java + ) { validator.validateStrAttr(pullParser, attr, name) } assertEquals(expectedAttrComponentNameErrorMsg(name), e1.message) - val e2 = assertThrows("$name is not valid Java class name", - SecurityException::class.java) { + val e2 = assertThrows( + "$name is not valid Java class name", + SecurityException::class.java + ) { validator.validateResStrAttr(pullParser, index, name) } assertEquals(expectedAttrComponentNameErrorMsg(name), e2.message) @@ -437,15 +551,19 @@ class AndroidPackageParsingValidationTest { try { validator.validateStrAttr(pullParser, name, value) } catch (e: SecurityException) { - fail("Failed to parse valid <$tag> attribute $name with max length of $maxLen:" + - " ${e.message}") + fail( + "Failed to parse valid <$tag> attribute $name with max length of $maxLen:" + + " ${e.message}" + ) } if (index != null) { try { validator.validateResStrAttr(pullParser, index, value) } catch (e: SecurityException) { - fail("Failed to parse valid <$tag> resource string attribute $name with max" + - " length of $maxLen: ${e.message}") + fail( + "Failed to parse valid <$tag> resource string attribute $name with max" + + " length of $maxLen: ${e.message}" + ) } } } @@ -485,8 +603,10 @@ class AndroidPackageParsingValidationTest { try { parseXmlStr(xml) } catch (e: SecurityException) { - fail("Failed to parse <$tag> with max count limit of $maxNum under" + - " <$parentTag>: ${e.message}") + fail( + "Failed to parse <$tag> with max count limit of $maxNum under" + + " <$parentTag>: ${e.message}" + ) } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 6b225fc945d5..bdee99b484b9 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -10244,7 +10244,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { try { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), - r.getSbn().getTag(), r,false); + r.getSbn().getTag(), r, false, false); fail("Allowed a contextual direct reply with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good @@ -10275,7 +10275,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), - r.getSbn().getTag(), r,false); + r.getSbn().getTag(), r, false, false); } @Test @@ -10309,7 +10309,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), - r.getSbn().getTag(), r,false); + r.getSbn().getTag(), r, false, false); } @Test @@ -10522,7 +10522,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // normal blocked notifications - blocked assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // just using the style - blocked nb.setStyle(new Notification.MediaStyle()); @@ -10531,7 +10531,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // using the style, but incorrect type in session - blocked nb.setStyle(new Notification.MediaStyle()); @@ -10543,7 +10543,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // style + media session - bypasses block nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); @@ -10552,7 +10552,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test @@ -10635,7 +10635,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // normal blocked notifications - blocked assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // just using the style - blocked Person person = new Person.Builder() @@ -10649,36 +10649,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse(); // style + managed call - bypasses block when(mTelecomManager.isInManagedCall()).thenReturn(true); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); // style + self managed call - bypasses block when(mTelecomManager.isInSelfManagedCall( r.getSbn().getPackageName(), r.getUser())).thenReturn(true); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); // set telecom manager to null - blocked mService.setTelecomManager(null); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)) + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)) .isFalse(); // set telecom feature to false - blocked when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)) + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)) .isFalse(); // telecom manager is not ready - blocked mService.setTelecomManager(mTelecomManager); when(mTelecomManager.isInCall()).thenThrow(new IllegalStateException("not ready")); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)) + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)) .isFalse(); } @@ -11243,7 +11243,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { try { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false); assertFalse("CallStyle should not be allowed without a valid use case", true); } catch (IllegalArgumentException error) { assertThat(error.getMessage()).contains("CallStyle"); @@ -11263,7 +11263,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); + } + + @Test + public void checkCallStyleNotification_allowedForByForegroundService() throws Exception { + Person person = new Person.Builder().setName("caller").build(); + Notification n = new Notification.Builder(mContext, "test") + // Without FLAG_FOREGROUND_SERVICE. + //.setFlag(FLAG_FOREGROUND_SERVICE, true) + .setStyle(Notification.CallStyle.forOngoingCall( + person, mock(PendingIntent.class))) + .build(); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false, + true /* byForegroundService */)).isTrue(); } @Test @@ -11279,7 +11297,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test @@ -11295,7 +11313,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test @@ -11311,7 +11329,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), - r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } @Test diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 54995fb5bd84..edc5df20231a 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1022,9 +1022,12 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100) .compose(); CombinedVibration effect = CombinedVibration.createParallel(composed); - long vibrationId = startThreadAndDispatcher(effect); - + // We create the HalVibration here to obtain the vibration id and use it to mock the + // required response when calling triggerSyncedVibration. + HalVibration halVibration = createVibration(effect); + long vibrationId = halVibration.id; when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); + startThreadAndDispatcher(halVibration); assertTrue(waitUntil( () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty() @@ -1056,7 +1059,6 @@ public class VibrationThreadTest { mVibratorProviders.get(4).setSupportedPrimitives( VibrationEffect.Composition.PRIMITIVE_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); - when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true); VibrationEffect composed = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) @@ -1067,7 +1069,12 @@ public class VibrationThreadTest { .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1)) .addVibrator(4, composed) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + // We create the HalVibration here to obtain the vibration id and use it to mock the + // required response when calling triggerSyncedVibration. + HalVibration halVibration = createVibration(effect); + long vibrationId = halVibration.id; + when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); + startThreadAndDispatcher(halVibration); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1117,13 +1124,17 @@ public class VibrationThreadTest { mockVibrators(vibratorIds); mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK); when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true); - when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false); CombinedVibration effect = CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + // We create the HalVibration here to obtain the vibration id and use it to mock the + // required response when calling triggerSyncedVibration. + HalVibration halVibration = createVibration(effect); + long vibrationId = halVibration.id; + when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false); + startThreadAndDispatcher(halVibration); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1492,7 +1503,6 @@ public class VibrationThreadTest { assertTrue(fakeVibrator.getAmplitudes().isEmpty()); } - @FlakyTest @Test public void vibrate_multipleVibrations_withCancel() throws Exception { mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects( diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java index d6c821f5aa76..8fadecd0dc38 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java @@ -19,8 +19,7 @@ package com.android.server.policy; import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.LargeTest; +import androidx.test.filters.MediumTest; import com.android.internal.annotations.Keep; import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent; @@ -33,7 +32,7 @@ import junitparams.JUnitParamsRunner; import junitparams.Parameters; @Presubmit -@LargeTest +@MediumTest @RunWith(JUnitParamsRunner.class) public class ShortcutLoggingTests extends ShortcutKeyTestBase { @@ -223,7 +222,7 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { @Before public void setUp() { - setUpPhoneWindowManager(); + setUpPhoneWindowManager(/*supportSettingsUpdate*/ true); mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID); mPhoneWindowManager.overrideLaunchHome(); mPhoneWindowManager.overrideSearchKeyBehavior( @@ -235,7 +234,6 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { } @Test - @FlakyTest(bugId = 293273386) @Parameters(method = "shortcutTestArguments") public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent, int expectedKey, int expectedModifierState) { diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 7e7a9e153ec8..ef3a6edf9759 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -185,11 +185,10 @@ class TestPhoneWindowManager { mHandler = new Handler(mHandlerThread.getLooper()); mContext = mockingDetails(context).isSpy() ? context : spy(context); mHandler.runWithScissors(() -> setUp(supportSettingsUpdate), 0 /* timeout */); + waitForIdle(); } private void setUp(boolean supportSettingsUpdate) { - mPhoneWindowManager = spy(new PhoneWindowManager()); - // Use stubOnly() to reduce memory usage if it doesn't need verification. final MockSettings spyStubOnly = withSettings().stubOnly() .defaultAnswer(CALLS_REAL_METHODS); @@ -200,6 +199,8 @@ class TestPhoneWindowManager { .strictness(Strictness.LENIENT) .startMocking(); + mPhoneWindowManager = spy(new PhoneWindowManager()); + doReturn(mWindowManagerInternal).when( () -> LocalServices.getService(eq(WindowManagerInternal.class))); doReturn(mActivityManagerInternal).when( diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java index 1c6408bd6da7..56c3ec0ffe2d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java @@ -50,7 +50,6 @@ import android.platform.test.annotations.Presubmit; import android.util.Log; import android.view.IWindowManager; -import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import com.android.server.am.AssistDataRequester; @@ -154,7 +153,6 @@ public class AssistDataRequesterTest { .noteOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString(), any(), any()); } - @FlakyTest(bugId = 280107567) @Test public void testRequestData() throws Exception { setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, @@ -262,7 +260,6 @@ public class AssistDataRequesterTest { assertReceivedDataCount(0, 1, 0, 1); } - @FlakyTest(bugId = 280107567) @Test public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception { setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, @@ -274,7 +271,6 @@ public class AssistDataRequesterTest { assertReceivedDataCount(5, 5, 0, 0); } - @FlakyTest(bugId = 280107567) @Test public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception { setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED, @@ -321,7 +317,8 @@ public class AssistDataRequesterTest { assertEquals("Expected " + numPendingScreenshots + " pending screenshots, got " + mDataRequester.getPendingScreenshotCount(), numPendingScreenshots, mDataRequester.getPendingScreenshotCount()); - assertFalse("Expected request NOT completed", mCallbacks.mRequestCompleted); + assertEquals("Expected request NOT completed, unless no pending data", + numPendingData == 0 && numPendingScreenshots == 0, mCallbacks.mRequestCompleted); mGate.countDown(); waitForIdle(mHandler); assertEquals("Expected " + numReceivedData + " data, received " @@ -380,14 +377,7 @@ public class AssistDataRequesterTest { @Override public void onAssistRequestCompleted() { - mHandler.post(() -> { - try { - mGate.await(10, TimeUnit.SECONDS); - mRequestCompleted = true; - } catch (InterruptedException e) { - Log.e(TAG, "Failed to wait", e); - } - }); + mRequestCompleted = true; } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 42422d91d598..939ff97ab02e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -74,6 +74,7 @@ import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; +import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.UiThread; @@ -880,6 +881,33 @@ public class DisplayRotationTests { SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); } + @Test + public void sensorRotation_locked_halfFolded_configOff_rotationUnchanged() throws Exception { + mBuilder.setIsFoldable(true); + mBuilder.setSupportHalfFoldAutoRotateOverride(false); + mBuilder.build(); + configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false); + + enableOrientationSensor(); + + mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN); + freezeRotation(Surface.ROTATION_270); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0)); + assertTrue(waitForUiHandler()); + // No rotation... + assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); + + // ... half-fold -> still no rotation + mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED); + assertTrue(waitForUiHandler()); + verify(sMockWm).updateRotation(false, false); + assertTrue(waitForUiHandler()); + assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); + } + // ================================= // Tests for Policy based Rotation // ================================= @@ -1017,7 +1045,7 @@ public class DisplayRotationTests { @Test public void testSensorRotationAfterDisplayChangeBeforeTimeout_ignoresSensor() throws Exception { - mBuilder.setSupportHalfFoldAutoRotateOverride(true) + mBuilder.setIsFoldable(true) .setPauseRotationWhenUnfolding(true) .setDisplaySwitchRotationBlockTimeMs(1000) .build(); @@ -1035,7 +1063,7 @@ public class DisplayRotationTests { @Test public void testSensorRotationAfterDisplayChangeAfterTimeout_usesSensor() throws Exception { - mBuilder.setSupportHalfFoldAutoRotateOverride(true) + mBuilder.setIsFoldable(true) .setPauseRotationWhenUnfolding(true) .setDisplaySwitchRotationBlockTimeMs(1000) .build(); @@ -1053,7 +1081,7 @@ public class DisplayRotationTests { @Test public void testSensorRotationAfterHingeEventBeforeTimeout_ignoresSensor() throws Exception { - mBuilder.setSupportHalfFoldAutoRotateOverride(true) + mBuilder.setIsFoldable(true) .setPauseRotationWhenUnfolding(true) .setMaxHingeAngle(165) .setHingeAngleRotationBlockTimeMs(400) @@ -1073,7 +1101,7 @@ public class DisplayRotationTests { @Test public void testSensorRotationAfterHingeEventBeforeTimeoutFlagDisabled_usesSensorData() throws Exception { - mBuilder.setSupportHalfFoldAutoRotateOverride(true) + mBuilder.setIsFoldable(true) .setPauseRotationWhenUnfolding(false) .setMaxHingeAngle(165) .setHingeAngleRotationBlockTimeMs(400) @@ -1092,7 +1120,7 @@ public class DisplayRotationTests { @Test public void testSensorRotationAfterHingeEventAfterTimeout_usesSensorData() throws Exception { - mBuilder.setSupportHalfFoldAutoRotateOverride(true) + mBuilder.setIsFoldable(true) .setPauseRotationWhenUnfolding(true) .setMaxHingeAngle(165) .setHingeAngleRotationBlockTimeMs(400) @@ -1112,7 +1140,7 @@ public class DisplayRotationTests { @Test public void testSensorRotationAfterLargeHingeEventBeforeTimeout_usesSensor() throws Exception { - mBuilder.setSupportHalfFoldAutoRotateOverride(true) + mBuilder.setIsFoldable(true) .setPauseRotationWhenUnfolding(true) .setMaxHingeAngle(165) .setHingeAngleRotationBlockTimeMs(400) @@ -1254,6 +1282,7 @@ public class DisplayRotationTests { private int mCarDockRotation; private int mDeskDockRotation; private int mUndockedHdmiRotation; + private boolean mIsFoldable; private DisplayRotationBuilder setIsDefaultDisplay(boolean isDefaultDisplay) { mIsDefaultDisplay = isDefaultDisplay; @@ -1308,9 +1337,17 @@ public class DisplayRotationTests { return this; } + private DisplayRotationBuilder setIsFoldable(boolean value) { + mIsFoldable = value; + return this; + } + private DisplayRotationBuilder setSupportHalfFoldAutoRotateOverride( boolean supportHalfFoldAutoRotateOverride) { mSupportHalfFoldAutoRotateOverride = supportHalfFoldAutoRotateOverride; + if (supportHalfFoldAutoRotateOverride) { + mIsFoldable = true; + } return this; } @@ -1455,6 +1492,11 @@ public class DisplayRotationTests { when(mMockContext.getResources().getBoolean( com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride)) .thenReturn(mSupportHalfFoldAutoRotateOverride); + + when(mMockContext.getResources().getIntArray( + R.array.config_foldedDeviceStates)) + .thenReturn(mIsFoldable ? new int[]{0} : new int[]{}); + mMockDisplayRotationReversionController = mock(DisplayRotationReversionController.class); when(mMockDisplayContent.getRotationReversionController()) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java index 8bc4cedf6fce..db08eabac699 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; import android.view.InputChannel; +import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; @@ -72,6 +73,7 @@ public class TaskPositioningControllerTests extends WindowTestsBase { doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor(); } + @FlakyTest(bugId = 291067614) @Test public void testStartAndFinishPositioning() { assertFalse(mTarget.isPositioningLocked()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index e3d1b9c669f9..6305bb621bc8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -47,7 +47,6 @@ import static org.mockito.Mockito.verify; import android.content.pm.ActivityInfo; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; @@ -65,9 +64,9 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; import java.util.List; @@ -85,14 +84,6 @@ public class WallpaperControllerTests extends WindowTestsBase { private static final int INITIAL_HEIGHT = 900; private static final int SECOND_WIDTH = 300; - @Before - public void setup() { - Resources resources = mWm.mContext.getResources(); - spyOn(resources); - doReturn(false).when(resources).getBoolean( - com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay); - } - @Test public void testWallpaperScreenshot() { WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class); @@ -190,16 +181,24 @@ public class WallpaperControllerTests extends WindowTestsBase { spyOn(dc.mWallpaperController); doReturn(true).when(dc.mWallpaperController).isWallpaperVisible(); - + dc.mWallpaperController.setMinWallpaperScale(.6f); + dc.mWallpaperController.setMaxWallpaperScale(1.2f); dc.mWallpaperController.adjustWallpaperWindows(); + spyOn(wallpaperWindow); spyOn(wallpaperWindow.mClient); float zoom = .5f; + float zoomScale = .9f; + wallpaperWindow.mShouldScaleWallpaper = true; + dc.mWallpaperController.setWallpaperZoomOut(homeWindow, zoom); assertEquals(zoom, wallpaperWindow.mWallpaperZoomOut, .01f); - verify(wallpaperWindow.mClient).dispatchWallpaperOffsets(anyFloat(), anyFloat(), anyFloat(), - anyFloat(), eq(zoom), anyBoolean()); + verify(wallpaperWindow.mClient) + .dispatchWallpaperOffsets( + anyFloat(), anyFloat(), anyFloat(), anyFloat(), eq(zoom), anyBoolean()); + verify(wallpaperWindow) + .setWallpaperOffset(anyInt(), anyInt(), AdditionalMatchers.eq(zoomScale, .01f)); } @Test @@ -213,9 +212,12 @@ public class WallpaperControllerTests extends WindowTestsBase { spyOn(dc.mWallpaperController); doReturn(true).when(dc.mWallpaperController).isWallpaperVisible(); + dc.mWallpaperController.setMinWallpaperScale(.6f); + dc.mWallpaperController.setMaxWallpaperScale(1.2f); dc.mWallpaperController.adjustWallpaperWindows(); + spyOn(wallpaperWindow); spyOn(wallpaperWindow.mClient); float newZoom = .5f; @@ -227,6 +229,9 @@ public class WallpaperControllerTests extends WindowTestsBase { assertEquals(1f, wallpaperWindow.mWallpaperScale, .01f); verify(wallpaperWindow.mClient).dispatchWallpaperOffsets(anyFloat(), anyFloat(), anyFloat(), anyFloat(), eq(newZoom), anyBoolean()); + // As the expected scale is .9 with a zoom of .5f and min and max scale of .6 and 1.2, + // if it's passing a scale of 1 it's not scaling the wallpaper. + verify(wallpaperWindow).setWallpaperOffset(anyInt(), anyInt(), eq(1f)); } @Test diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 040c5b013c6a..a4ad0cefaf80 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1319,8 +1319,11 @@ public class SubscriptionManager { private final Context mContext; // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing - // the Context and subId. - private static final Map<Pair<Context, Integer>, Resources> sResourcesCache = + // the Package Name and subId. Applications can create new contexts from + // {@link android.content.Context#createPackageContext} with the same resources for different + // purposes. Therefore, Cache can be wasted for resources from different contexts in the same + // package. Use the package name rather than the context itself as a key value of cache. + private static final Map<Pair<String, Integer>, Resources> sResourcesCache = new ConcurrentHashMap<>(); /** @@ -2809,12 +2812,13 @@ public class SubscriptionManager { boolean useRootLocale) { // Check if resources for this context and subId already exist in the resource cache. // Resources that use the root locale are not cached. - Pair<Context, Integer> cacheKey = null; + Pair<String, Integer> cacheKey = null; if (isValidSubscriptionId(subId) && !useRootLocale) { - cacheKey = Pair.create(context, subId); - if (sResourcesCache.containsKey(cacheKey)) { + cacheKey = Pair.create(context.getPackageName(), subId); + Resources cached = sResourcesCache.get(cacheKey); + if (cached != null) { // Cache hit. Use cached Resources. - return sResourcesCache.get(cacheKey); + return cached; } } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 1b5c53749b9b..26c17a461cf2 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -399,6 +399,11 @@ public class ApnSetting implements Parcelable { public @interface ProtocolType {} // Possible values for MVNO type. + /** + * Default value for MVNO type if it's not set. + * @hide + */ + public static final int MVNO_TYPE_UNKNOWN = -1; /** MVNO type for service provider name. */ public static final int MVNO_TYPE_SPN = 0; /** MVNO type for IMSI. */ @@ -410,10 +415,11 @@ public class ApnSetting implements Parcelable { /** @hide */ @IntDef(prefix = { "MVNO_TYPE_" }, value = { - MVNO_TYPE_SPN, - MVNO_TYPE_IMSI, - MVNO_TYPE_GID, - MVNO_TYPE_ICCID, + MVNO_TYPE_UNKNOWN, + MVNO_TYPE_SPN, + MVNO_TYPE_IMSI, + MVNO_TYPE_GID, + MVNO_TYPE_ICCID, }) @Retention(RetentionPolicy.SOURCE) public @interface MvnoType {} @@ -1480,7 +1486,7 @@ public class ApnSetting implements Parcelable { String mvnoTypeString = TextUtils.isEmpty(mvnoType) ? mvnoType : mvnoType.toLowerCase(Locale.ROOT); Integer mvnoTypeInt = MVNO_TYPE_STRING_MAP.get(mvnoTypeString); - return mvnoTypeInt == null ? UNSPECIFIED_INT : mvnoTypeInt; + return mvnoTypeInt == null ? MVNO_TYPE_UNKNOWN : mvnoTypeInt; } /** @hide */ @@ -1755,7 +1761,7 @@ public class ApnSetting implements Parcelable { private int mMaxConns; private int mWaitTime; private int mMaxConnsTime; - private int mMvnoType = UNSPECIFIED_INT; + private int mMvnoType = MVNO_TYPE_UNKNOWN; private String mMvnoMatchData; private int mApnSetId; private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 7381a85f4339..387dcfe2ddf3 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -425,6 +425,7 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { if (force_sparse_encoding_) { table_flattener_options_.sparse_entries = SparseEntriesMode::Forced; } + table_flattener_options_.use_compact_entries = enable_compact_entries_; if (resources_config_path_) { if (!ExtractResourceConfig(*resources_config_path_, &context, table_flattener_options_)) { return 1; diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h index 15fe11fd91cc..9452e588953e 100644 --- a/tools/aapt2/cmd/Convert.h +++ b/tools/aapt2/cmd/Convert.h @@ -46,6 +46,10 @@ class ConvertCommand : public Command { "This decreases APK size at the cost of resource retrieval performance.\n" "Applies sparse encoding to all resources regardless of minSdk.", &force_sparse_encoding_); + AddOptionalSwitch( + "--enable-compact-entries", + "This decreases APK size by using compact resource entries for simple data types.", + &enable_compact_entries_); AddOptionalSwitch("--keep-raw-values", android::base::StringPrintf("Preserve raw attribute values in xml files when using the" " '%s' output format", kOutputFormatBinary), @@ -85,6 +89,7 @@ class ConvertCommand : public Command { bool verbose_ = false; bool enable_sparse_encoding_ = false; bool force_sparse_encoding_ = false; + bool enable_compact_entries_ = false; std::optional<std::string> resources_config_path_; }; diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index be877660ef72..3d83caf29bba 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -21,6 +21,7 @@ #include <string> #include <vector> +#include "android-base/parseint.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" #include "androidfw/BigBuffer.h" @@ -229,14 +230,29 @@ std::string GetToolFingerprint() { static const char* const sMinorVersion = "19"; // The build id of aapt2 binary. - static std::string sBuildId = android::build::GetBuildNumber(); - - if (android::base::StartsWith(sBuildId, "eng.")) { - time_t now = time(0); - tm* ltm = localtime(&now); + static const std::string sBuildId = [] { + std::string buildNumber = android::build::GetBuildNumber(); + + if (android::base::StartsWith(buildNumber, "eng.")) { + // android::build::GetBuildNumber() returns something like "eng.user.20230725.214219" where + // the latter two parts are "yyyyMMdd.HHmmss" at build time. Use "yyyyMM" in the fingerprint. + std::vector<std::string> parts = util::Split(buildNumber, '.'); + int buildYear; + int buildMonth; + if (parts.size() < 3 || parts[2].length() < 6 || + !android::base::ParseInt(parts[2].substr(0, 4), &buildYear) || + !android::base::ParseInt(parts[2].substr(4, 2), &buildMonth)) { + // Fallback to localtime() if GetBuildNumber() returns an unexpected output. + time_t now = time(0); + tm* ltm = localtime(&now); + buildYear = 1900 + ltm->tm_year; + buildMonth = 1 + ltm->tm_mon; + } - sBuildId = android::base::StringPrintf("eng.%d%d", 1900 + ltm->tm_year, 1 + ltm->tm_mon); - } + buildNumber = android::base::StringPrintf("eng.%04d%02d", buildYear, buildMonth); + } + return buildNumber; + }(); return android::base::StringPrintf("%s.%s-%s", sMajorVersion, sMinorVersion, sBuildId.c_str()); } |