Merge "Revert^3 "Revert "Load native GLES driver when specified.""" into main
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
index f92c297..dca818e 100644
--- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
@@ -17,10 +17,14 @@
package android.surfaceflinger;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.LargeTest;
@@ -194,4 +198,16 @@
mTransaction.apply(true);
}
}
+
+ @Test
+ public void bufferQueue() throws Exception {
+ SurfaceView testSV = mActivity.mTestSurfaceView;
+ SurfaceHolder holder = testSV.getHolder();
+ holder.getSurface();
+ for (int i = 0; i < sProfilingIterations; i++) {
+ Canvas canvas = holder.lockCanvas();
+ holder.unlockCanvasAndPost(canvas);
+ mTransaction.apply(true);
+ }
+ }
}
diff --git a/core/api/current.txt b/core/api/current.txt
index df8f581..36c2f01 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9635,9 +9635,16 @@
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 @@
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 77168e3..0185080 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -647,6 +647,7 @@
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 bcd43ea..096ad55 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2256,6 +2256,7 @@
*
* @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 d189bab..2c428ef 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -236,6 +236,7 @@
* {@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/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index f660377..03e75e9 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 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 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 @@
* 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 @@
* 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 @@
}
}
+ /**
+ * 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 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 fa68508..2a311bf 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -22,4 +22,5 @@
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 a0bbeb5..c86ccfd 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1483,6 +1483,12 @@
// 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 @@
// 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 eb928fd..a86c0c9 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -775,8 +775,6 @@
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 e9c59f5..2061c2b 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -48,6 +48,15 @@
/**
* 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/provider/Settings.java b/core/java/android/provider/Settings.java
index cb46674..2abf02e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2120,6 +2120,21 @@
"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 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 @@
* @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/voice/AbstractDetector.java b/core/java/android/service/voice/AbstractDetector.java
index 8648e38..7af7fe6 100644
--- a/core/java/android/service/voice/AbstractDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -65,6 +65,13 @@
*/
private final IBinder mToken = new Binder();
+ /**
+ * A flag controls whether attributionTag will be passed into the Identity.
+ * TODO(b/289087412): This flag will be converted and confirm to the trunk stable flag
+ * configuration.
+ */
+ static final boolean IS_IDENTITY_WITH_ATTRIBUTION_TAG = false;
+
AbstractDetector(
IVoiceInteractionManagerService managerService,
Executor executor,
@@ -153,12 +160,16 @@
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@NonNull IHotwordRecognitionStatusCallback callback,
- int detectorType) {
+ int detectorType,
+ @Nullable String attributionTag) {
if (DEBUG) {
Slog.d(TAG, "initAndVerifyDetector()");
}
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
+ if (IS_IDENTITY_WITH_ATTRIBUTION_TAG) {
+ identity.attributionTag = attributionTag;
+ }
try {
mManagerService.initAndVerifyDetector(identity, options, sharedMemory, mToken, callback,
detectorType);
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 9d28334..21f676e 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -324,6 +324,7 @@
private final Handler mHandler;
private final IBinder mBinder = new Binder();
private final boolean mSupportSandboxedDetectionService;
+ private final String mAttributionTag;
@GuardedBy("mLock")
private boolean mIsAvailabilityOverriddenByTestApi = false;
@@ -846,13 +847,17 @@
* @param targetSdkVersion The target SDK version.
* @param SupportSandboxedDetectionService {@code true} if HotwordDetectionService should be
* triggered, otherwise {@code false}.
+ * @param attributionTag an optional attribution tag passed form the
+ * {@link VoiceInteractionService} context via the
+ * {@link createAlwaysOnHotwordDetectorInternal(String, Locale, boolean, PersistableBundle,
+ * SharedMemory, ModuleProperties, Executor, Callback)}.
*
* @hide
*/
public AlwaysOnHotwordDetector(String text, Locale locale, Executor executor, Callback callback,
KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
- boolean supportSandboxedDetectionService) {
+ boolean supportSandboxedDetectionService, @Nullable String attributionTag) {
super(modelManagementService, executor, callback);
mHandler = new MyHandler(Looper.getMainLooper());
@@ -865,6 +870,7 @@
mInternalCallback = new SoundTriggerListener(mHandler);
mModelManagementService = modelManagementService;
mSupportSandboxedDetectionService = supportSandboxedDetectionService;
+ mAttributionTag = attributionTag;
}
// Do nothing. This method should not be abstract.
@@ -876,11 +882,14 @@
@Nullable SoundTrigger.ModuleProperties moduleProperties) {
if (mSupportSandboxedDetectionService) {
initAndVerifyDetector(options, sharedMemory, mInternalCallback,
- DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
+ DETECTOR_TYPE_TRUSTED_HOTWORD_DSP, mAttributionTag);
}
try {
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
+ if (IS_IDENTITY_WITH_ATTRIBUTION_TAG) {
+ identity.attributionTag = mAttributionTag;
+ }
if (moduleProperties == null) {
moduleProperties = mModelManagementService
.listModuleProperties(identity)
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 7ab4faf..128bc0d 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -56,12 +56,14 @@
private final HotwordDetector.Callback mCallback;
private final AudioFormat mAudioFormat;
private final Executor mExecutor;
+ private final String mAttributionTag;
SoftwareHotwordDetector(
IVoiceInteractionManagerService managerService,
AudioFormat audioFormat,
Executor executor,
- HotwordDetector.Callback callback) {
+ HotwordDetector.Callback callback,
+ String attributionTag) {
super(managerService, executor, callback);
mManagerService = managerService;
@@ -69,13 +71,14 @@
mCallback = callback;
mExecutor = executor != null ? executor : new HandlerExecutor(
new Handler(Looper.getMainLooper()));
+ mAttributionTag = attributionTag;
}
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
initAndVerifyDetector(options, sharedMemory,
new InitializationStateListener(mExecutor, mCallback),
- DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+ DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE, mAttributionTag);
}
void onDetectorRemoteException() {
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index 93b7964..9e0eb4b 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -60,15 +60,17 @@
private final Executor mExecutor;
private final IVoiceInteractionManagerService mManagerService;
private final VisualQueryDetectorInitializationDelegate mInitializationDelegate;
+ private final String mAttributionTag;
VisualQueryDetector(
IVoiceInteractionManagerService managerService,
@NonNull @CallbackExecutor Executor executor,
- Callback callback) {
+ Callback callback, @Nullable String attributionTag) {
mManagerService = managerService;
mCallback = callback;
mExecutor = executor;
mInitializationDelegate = new VisualQueryDetectorInitializationDelegate();
+ mAttributionTag = attributionTag;
}
/**
@@ -246,7 +248,7 @@
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
initAndVerifyDetector(options, sharedMemory,
new InitializationStateListener(mExecutor, mCallback),
- DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
+ DETECTOR_TYPE_VISUAL_QUERY_DETECTOR, mAttributionTag);
}
@Override
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index ab9ae0a..8cec17f 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -734,7 +734,7 @@
AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale,
executor, callback, mKeyphraseEnrollmentInfo, mSystemService,
getApplicationContext().getApplicationInfo().targetSdkVersion,
- supportHotwordDetectionService);
+ supportHotwordDetectionService, getAttributionTag());
mActiveDetectors.add(dspDetector);
try {
@@ -895,7 +895,7 @@
SoftwareHotwordDetector softwareHotwordDetector =
new SoftwareHotwordDetector(mSystemService, /* audioFormat= */ null,
- executor, callback);
+ executor, callback, getAttributionTag());
mActiveDetectors.add(softwareHotwordDetector);
try {
@@ -965,7 +965,8 @@
}
VisualQueryDetector visualQueryDetector =
- new VisualQueryDetector(mSystemService, executor, callback);
+ new VisualQueryDetector(mSystemService, executor, callback,
+ getAttributionTag());
HotwordDetector visualQueryDetectorInitializationDelegate =
visualQueryDetector.getInitializationDelegate();
mActiveDetectors.add(visualQueryDetectorInitializationDelegate);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index e47fab4..c7e5453 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -41,6 +41,9 @@
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 @@
// 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 @@
* @hide
*/
public boolean shouldZoomOutWallpaper() {
- return false;
+ return mIsWearOs && !CompatChanges.isChangeEnabled(WEAROS_WALLPAPER_HANDLES_SCALING);
}
/**
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 0a9bab5..1cde742 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -253,7 +253,11 @@
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 0c6d6f9..965277c 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -196,7 +196,8 @@
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 @@
"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 @@
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/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index cf0488b..b0d9b67 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -33,6 +33,14 @@
<!-- Maximum velocity to initiate a fling, as measured in dips per second. -->
<dimen name="config_viewMaxFlingVelocity">8000dp</dimen>
+ <!-- Minimum velocity (absolute value) to initiate a fling from a rotary encoder device, as
+ measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
+ <dimen name="config_viewMinRotaryEncoderFlingVelocity">500dp</dimen>
+
+ <!-- Maximum velocity (absolute value) to initiate a fling from a rotary encoder device, as
+ measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
+ <dimen name="config_viewMaxRotaryEncoderFlingVelocity">8000dp</dimen>
+
<!-- Number of notifications to keep in the notification service historical archive.
Reduced intentionally for watches to retain minimal memory footprint -->
<integer name="config_notificationServiceArchiveSize">1</integer>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 64be87b..38da090 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>
@@ -6591,4 +6587,7 @@
vibrations for the customized haptic feedback IDs, and continue to use the system defaults
for the non-customized ones. -->
<string name="config_hapticFeedbackCustomizationFile" />
+
+ <!-- Whether or not ActivityManager PSS profiling is disabled. -->
+ <bool name="config_am_disablePssProfiling">false</bool>
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index b7a5bc8..18abe70 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 3aae3ca..5806e93 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"/>
@@ -5186,4 +5185,7 @@
<java-symbol type="bool" name="config_enable_a11y_magnification_single_panning" />
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
+
+ <!-- For ActivityManager PSS profiling configurability -->
+ <java-symbol type="bool" name="config_am_disablePssProfiling" />
</resources>
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 893c87d..354f10a 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -43,6 +43,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 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 0000000..02b7075
--- /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 fb1980a..7e0c207 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 f2a0785..b2ec98b 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/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 6718565..e698601 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 @@
+ " 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 @@
}
/**
- * 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 @@
}
}
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/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index d972f48..16c3960 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.dagger.pip;
-import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
@@ -229,7 +228,6 @@
@WMSingleton
@Provides
- @Nullable
static PipTransition providePipTransition(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 2ded4a3..04032bb1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -36,10 +36,13 @@
public abstract class PipModule {
@WMSingleton
@Provides
- @Nullable
static PipTransitionController providePipTransitionController(
com.android.wm.shell.pip.PipTransition legacyPipTransition,
- com.android.wm.shell.pip2.PipTransition newPipTransition) {
- return PipUtils.isPip2ExperimentEnabled() ? newPipTransition : legacyPipTransition;
+ @Nullable com.android.wm.shell.pip2.PipTransition newPipTransition) {
+ if (PipUtils.isPip2ExperimentEnabled() && newPipTransition != null) {
+ return newPipTransition;
+ } else {
+ return legacyPipTransition;
+ }
}
}
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 39b6675..88a81fc 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 @@
} 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 064af04..a743e99 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 @@
// 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 @@
}
}
+ /** 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 @@
*/
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 @@
* 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 d978eaf..d07d2b7b6 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 @@
.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/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 4cc755b..2b19da2 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
@@ -374,6 +374,11 @@
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 b67acd5..672e57a 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 @@
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 @@
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 @@
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 @@
}
}
+ 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/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index 67d5718..1e5e42f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -66,7 +66,8 @@
*/
@Postsubmit
@Test
- fun letterboxAppFocusedAtEnd() = flicker.assertEventLog { focusChanges(letterboxApp.`package`) }
+ fun letterboxAppFocusedAtEnd() =
+ flicker.assertEventLog { focusChanges(letterboxApp.packageName) }
@Postsubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 5c7d1d8..97147a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -47,7 +47,7 @@
private val uid =
context.packageManager
- .getApplicationInfo(testApp.`package`, PackageManager.ApplicationInfoFlags.of(0))
+ .getApplicationInfo(testApp.packageName, PackageManager.ApplicationInfoFlags.of(0))
.uid
@JvmOverloads
@@ -57,7 +57,7 @@
return {
setup {
notifyManager.setBubblesAllowed(
- testApp.`package`,
+ testApp.packageName,
uid,
NotificationManager.BUBBLE_PREFERENCE_ALL
)
@@ -68,7 +68,7 @@
teardown {
notifyManager.setBubblesAllowed(
- testApp.`package`,
+ testApp.packageName,
uid,
NotificationManager.BUBBLE_PREFERENCE_NONE
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 6d20740..dfa3696 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -117,7 +117,7 @@
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
}
companion object {
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 5efa51b..421ad75 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 @@
}
/** 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/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
index 000ae8f..c6cbcd0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -26,7 +26,7 @@
/** Helper class for PIP app on AndroidTV */
open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
- private val appSelector = By.pkg(`package`).depth(0)
+ private val appSelector = By.pkg(packageName).depth(0)
val ui: UiObject2?
get() = uiDevice.findObject(appSelector)
@@ -46,7 +46,7 @@
}
override fun clickObject(resId: String) {
- val selector = By.res(`package`, resId)
+ val selector = By.res(packageName, resId)
focusOnObject(selector) || error("Could not focus on `$resId` object")
uiDevice.pressDPadCenter()
}
@@ -68,7 +68,7 @@
}
fun waitUntilClosed(): Boolean {
- val appSelector = By.pkg(`package`).depth(0)
+ val appSelector = By.pkg(packageName).depth(0)
return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
}
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 2494054..e37d806 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 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 57943ec..2a50912 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 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 6f0e202..d5da1a8 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 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 dac8fa2..7fdcb9b 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 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 baecc16..308e954 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 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 3063ea5..39e75bd 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 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 41660ba..e18da17 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 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 c41ffb7..00d60e7 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 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 afde55b..d7efbc8 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 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 3765fc4..4eece3f 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 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 1e128fd..d96b056 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 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 7767872..809b690 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 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 4ca4bd1..bbdf2d7 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 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 2d9d258..5c29fd8 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 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 c9282ac..a7398eb 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 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 68c6d6c..eae88ad 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 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 304529e..7e8ee04 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 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 53a6b44..9295c33 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 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 a467830..4b59e9f 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 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 1524233..5ff36d4 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 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 0389659..c0cb721 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 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 7fadf18..8c14088 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 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 148cc52..7b6614b 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 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 0641fb0..5df5be9 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 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 741f871..9d63003 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 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 778e2d6..9fa04b2 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 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 dcdca70..9386aa2 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 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 3c69311..5ef2167 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 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 c6566f5..9caab9b 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.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 bb1a502..bf484e5 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.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/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index a3aae85..9b43816 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -61,7 +61,7 @@
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 5d67dc7..76fbf60 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -63,7 +63,7 @@
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
.getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
// TODO: Do we want this check in here? Add to the other tests?
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index ae5bb68..f8e43f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -61,7 +61,7 @@
open fun enterSplitScreenByDragFromTaskbar() {
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
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 91e7d47..e59ed64 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.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 @@
@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 @@
}
}
- /**
- * 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 10dceba..4c44028 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 @@
@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/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 80ccaa1..394864a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -47,7 +47,7 @@
tapl.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index a06ae6b..3b3be84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -53,7 +53,7 @@
.getAppIcon(secondaryApp.appName)
.openDeepShortcutMenu()
.getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index de4ec6d..eff3559 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -46,7 +46,7 @@
transitions {
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
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 50435a0..d098d33 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 @@
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 4c0850b..4ad3cd1 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -39,8 +39,8 @@
* 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 @@
* <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 @@
* 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 b3f72a1..73f15f2 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -30,12 +30,20 @@
/** 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 d0169b9..fe7cc3e 100644
--- a/media/java/android/media/LocalRingtonePlayer.java
+++ b/media/java/android/media/LocalRingtonePlayer.java
@@ -37,7 +37,7 @@
* @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 @@
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 @@
@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 @@
* 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 @@
}
/** 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 4323c73..1ee5aa3 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1057,7 +1057,7 @@
* 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 6b67d14..94a061a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -105,27 +105,26 @@
* <p>Uses {@link MediaRoute2Info#getId()} to set each entry's key.
*/
@GuardedBy("mLock")
- final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
+ private final Map<String, MediaRoute2Info> mRoutes = new ArrayMap<>();
+
+ private final RoutingController mSystemController;
@GuardedBy("mLock")
- @Nullable
- private RouteListingPreference mRouteListingPreference;
+ private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
- final RoutingController mSystemController;
+ private final AtomicInteger mNextRequestId = new AtomicInteger(1);
+ private final Handler mHandler;
@GuardedBy("mLock")
private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
@GuardedBy("mLock")
- MediaRouter2Stub mStub;
+ private MediaRouter2Stub mStub;
@GuardedBy("mLock")
- private final Map<String, RoutingController> mNonSystemRoutingControllers = new ArrayMap<>();
-
- private final AtomicInteger mNextRequestId = new AtomicInteger(1);
-
- final Handler mHandler;
+ @Nullable
+ private RouteListingPreference mRouteListingPreference;
/**
* Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback
@@ -314,21 +313,6 @@
}
/**
- * Returns whether any route in {@code routeList} has a same unique ID with given route.
- *
- * @hide
- */
- static boolean checkRouteListContainsRouteId(
- @NonNull List<MediaRoute2Info> routeList, @NonNull String routeId) {
- for (MediaRoute2Info info : routeList) {
- if (TextUtils.equals(routeId, info.getId())) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Gets the client package name of the app which this media router controls.
*
* <p>This will return null for non-system media routers.
@@ -439,14 +423,7 @@
* 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();
}
/**
@@ -1499,13 +1476,13 @@
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
+ if (containsRouteInfoWithId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
return;
}
List<MediaRoute2Info> selectableRoutes = getSelectableRoutes();
- if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) {
+ if (!containsRouteInfoWithId(selectableRoutes, route.getId())) {
Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
return;
}
@@ -1538,13 +1515,13 @@
}
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
+ if (!containsRouteInfoWithId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
return;
}
List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes();
- if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) {
+ if (!containsRouteInfoWithId(deselectableRoutes, route.getId())) {
Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
return;
}
@@ -1707,6 +1684,16 @@
}
}
+ /** Returns whether any route in {@code routeList} has a same unique ID with given route. */
+ private static boolean containsRouteInfoWithId(
+ @NonNull List<MediaRoute2Info> routeList, @NonNull String routeId) {
+ for (MediaRoute2Info info : routeList) {
+ if (TextUtils.equals(routeId, info.getId())) {
+ return true;
+ }
+ }
+ return false;
+ }
}
class SystemRoutingController extends RoutingController {
@@ -1950,6 +1937,8 @@
void setRouteListingPreference(@Nullable RouteListingPreference preference);
+ boolean showSystemOutputSwitcher();
+
List<MediaRoute2Info> getAllRoutes();
void setOnGetControllerHintsListener(OnGetControllerHintsListener listener);
@@ -2096,6 +2085,12 @@
"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() {
@@ -2908,6 +2903,18 @@
}
}
+ @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 3a6b398..0319f32 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -25,14 +25,11 @@
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 com.android.internal.annotations.VisibleForTesting;
-import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -100,68 +96,70 @@
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);
+ 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);
+ }
- // Local-only (not propagated to remote).
- mPreferBuiltinDevice = builder.mPreferBuiltinDevice; // System-only
- mAllowRemote = (mRemoteRingtoneService != null); // Always false for remote.
+ /**
+ * 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);
+ }
- // 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;
+ /**
+ * 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 @@
*/
@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 @@
*/
@Deprecated
public int getStreamType() {
- return AudioAttributes.toLegacyStreamType(mAudioAttributes);
+ return mApiImpl.getStreamType();
}
/**
@@ -201,17 +191,7 @@
*/
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 @@
*/
@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 @@
*/
@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 @@
* @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 @@
* @hide
*/
public boolean hasHapticChannels() {
- return (mActivePlayer == null) ? false : mActivePlayer.hasHapticChannels();
+ return mApiImpl.hasHapticChannels();
}
/**
@@ -371,7 +256,7 @@
* {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
*/
public AudioAttributes getAudioAttributes() {
- return mAudioAttributes;
+ return mApiImpl.getAudioAttributes();
}
/**
@@ -379,12 +264,7 @@
* @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 @@
* @return true if this player loops when playing.
*/
public boolean isLooping() {
- synchronized (mPlaybackSettingsLock) {
- return mIsLooping;
- }
+ return mApiImpl.isLooping();
}
/**
@@ -403,22 +281,7 @@
* 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 @@
* @return a value between 0.0f and 1.0f.
*/
public float getVolume() {
- synchronized (mPlaybackSettingsLock) {
- return mVolume;
- }
+ return mApiImpl.getVolume();
}
/**
@@ -439,16 +300,7 @@
* @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 @@
* @return true if the HapticGenerator is enabled.
*/
public boolean isHapticGeneratorEnabled() {
- synchronized (mPlaybackSettingsLock) {
- return mHapticGeneratorEnabled;
- }
+ return mApiImpl.isHapticGeneratorEnabled();
}
/**
@@ -468,8 +318,7 @@
* @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 @@
/** {@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 @@
* @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 @@
}
/**
- * 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 @@
}
}
+
+ /**
+ * 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 12766fb..0ad8c24 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -496,13 +496,32 @@
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 @@
* @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 @@
@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 0000000..3c54d4a
--- /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 0000000..f1a8155
--- /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 0c6096e..6e2aaab 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -130,7 +130,7 @@
/**
* 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 @@
* 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/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index d3cc4d1..cbb570e 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -20,4 +20,8 @@
sdk_version: "system_current",
min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.healthfitness",
+ ],
}
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 79f8c46..7f5948c 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -22,7 +22,7 @@
name: "SpaLib",
srcs: ["src/**/*.kt"],
-
+ use_resource_processor: true,
static_libs: [
"androidx.slice_slice-builders",
"androidx.slice_slice-core",
@@ -50,5 +50,6 @@
// 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 e4d56cc..65f5d34 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -22,7 +22,7 @@
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 4a7418f..eaeda3c 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -22,7 +22,7 @@
name: "SpaPrivilegedLib",
srcs: ["src/**/*.kt"],
-
+ use_resource_processor: true,
static_libs: [
"SpaLib",
"SettingsLib",
@@ -45,5 +45,6 @@
// 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 0000000..05cb1b1
--- /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 1a7d896..de2cf1f 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.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 @@
@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 @@
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 fab3ae8..cc3584b 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.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 ae362c8..e2ff7b0 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 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 @@
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 b43210f..cee750e 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 @@
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 3e96994..5fc1972 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.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 @@
@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 cbc4822..1fa854a 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.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 @@
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 b08b6df..e77dcd4 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 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 @@
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 26caa01..d11e63a 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.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 @@
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 f6f4889..82fbee9 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 @@
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 961ec10..457b810 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 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 @@
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 3ec5eba..363e20aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -133,6 +133,12 @@
}
}
+ @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 cb8e7e8..dcb0a07 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -23,6 +23,7 @@
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 @@
}
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 c967b56..9d533fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -700,4 +700,14 @@
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/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index a05a6e9..69b61c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -25,10 +25,13 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -39,6 +42,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.RequiresApi;
@@ -76,14 +80,16 @@
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
};
- private BluetoothLeBroadcast mService;
+ private BluetoothLeBroadcast mServiceBroadcast;
+ private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
private BluetoothLeAudioContentMetadata.Builder mBuilder;
private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
private String mAppSourceName = "";
private String mNewAppSourceName = "";
- private boolean mIsProfileReady;
+ private boolean mIsBroadcastProfileReady = false;
+ private boolean mIsBroadcastAssistantProfileReady = false;
private String mProgramInfo;
private byte[] mBroadcastCode;
private Executor mExecutor;
@@ -94,17 +100,22 @@
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (DEBUG) {
- Log.d(TAG, "Bluetooth service connected");
+ Log.d(TAG, "Bluetooth service connected: " + profile);
}
- if(!mIsProfileReady) {
- mService = (BluetoothLeBroadcast) proxy;
- mIsProfileReady = true;
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) {
+ mServiceBroadcast = (BluetoothLeBroadcast) proxy;
+ mIsBroadcastProfileReady = true;
registerServiceCallBack(mExecutor, mBroadcastCallback);
List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
if (!metadata.isEmpty()) {
updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
}
registerContentObserver();
+ } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ && !mIsBroadcastAssistantProfileReady) {
+ mIsBroadcastAssistantProfileReady = true;
+ mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+ registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
}
}
@@ -113,9 +124,17 @@
if (DEBUG) {
Log.d(TAG, "Bluetooth service disconnected");
}
- if(mIsProfileReady) {
- mIsProfileReady = false;
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) {
+ mIsBroadcastProfileReady = false;
unregisterServiceCallBack(mBroadcastCallback);
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ && mIsBroadcastAssistantProfileReady) {
+ mIsBroadcastAssistantProfileReady = false;
+ unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
+ }
+
+ if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
unregisterContentObserver();
}
}
@@ -157,6 +176,8 @@
"onBroadcastStopped(), reason = " + reason + ", broadcastId = "
+ broadcastId);
}
+
+ stopLocalSourceReceivers();
resetCacheInfo();
}
@@ -196,6 +217,61 @@
}
};
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new BluetoothLeBroadcastAssistant.Callback() {
+ @Override
+ public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {}
+ @Override
+ public void onSearchStarted(int reason) {}
+
+ @Override
+ public void onSearchStartFailed(int reason) {}
+
+ @Override
+ public void onSearchStopped(int reason) {}
+
+ @Override
+ public void onSearchStopFailed(int reason) {}
+
+ @Override
+ public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
+
+ @Override
+ public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source, int reason) {}
+
+ @Override
+ public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {}
+
+ @Override
+ public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {}
+
+ @Override
+ public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ if (DEBUG) {
+ Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = "
+ + reason + ", sourceId = " + sourceId);
+ }
+ }
+
+ @Override
+ public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ if (DEBUG) {
+ Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = "
+ + reason + ", sourceId = " + sourceId);
+ }
+ }
+
+ @Override
+ public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+ @NonNull BluetoothLeBroadcastReceiveState state) {}
+ };
+
private class BroadcastSettingsObserver extends ContentObserver {
BroadcastSettingsObserver(Handler h) {
super(h);
@@ -219,6 +295,9 @@
// Before registering callback, the constructor should finish creating the all of variables.
BluetoothAdapter.getDefaultAdapter()
.getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
+ BluetoothAdapter.getDefaultAdapter()
+ .getProfileProxy(context, mServiceListener,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
}
/**
@@ -227,7 +306,7 @@
*/
public void startBroadcast(String appSourceName, String language) {
mNewAppSourceName = appSourceName;
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast.");
return;
}
@@ -237,7 +316,7 @@
"startBroadcast: language = " + language + " ,programInfo = " + programInfo);
}
buildContentMetadata(language, programInfo);
- mService.startBroadcast(mBluetoothLeAudioContentMetadata,
+ mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata,
(mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
}
@@ -341,13 +420,13 @@
}
public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null");
return null;
}
if (mBluetoothLeBroadcastMetadata == null) {
final List<BluetoothLeBroadcastMetadata> metadataList =
- mService.getAllBroadcastMetadata();
+ mServiceBroadcast.getAllBroadcastMetadata();
mBluetoothLeBroadcastMetadata = metadataList.stream()
.filter(i -> i.getBroadcastId() == mBroadcastId)
.findFirst()
@@ -411,14 +490,14 @@
* corresponding callback {@link BluetoothLeBroadcast.Callback}.
*/
public void stopBroadcast(int broadcastId) {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast.");
return;
}
if (DEBUG) {
Log.d(TAG, "stopBroadcast()");
}
- mService.stopBroadcast(broadcastId);
+ mServiceBroadcast.stopBroadcast(broadcastId);
}
/**
@@ -426,7 +505,7 @@
* corresponding callback {@link BluetoothLeBroadcast.Callback}.
*/
public void updateBroadcast(String appSourceName, String language) {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
return;
}
@@ -437,26 +516,57 @@
}
mNewAppSourceName = appSourceName;
mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
- mService.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+ mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
}
public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcast.Callback callback) {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null.");
return;
}
- mService.registerCallback(executor, callback);
+ mServiceBroadcast.registerCallback(executor, callback);
+ }
+
+ /**
+ * Register Broadcast Assistant Callbacks to track it's state and receivers
+ *
+ * @param executor Executor object for callback
+ * @param callback Callback object to be registered
+ */
+ public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ if (mServiceBroadcastAssistant == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+ return;
+ }
+
+ mServiceBroadcastAssistant.registerCallback(executor, callback);
}
public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null.");
return;
}
- mService.unregisterCallback(callback);
+ mServiceBroadcast.unregisterCallback(callback);
+ }
+
+ /**
+ * Unregister previousely registered Broadcast Assistant Callbacks
+ *
+ * @param callback Callback object to be unregistered
+ */
+ public void unregisterBroadcastAssistantCallback(
+ @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ if (mServiceBroadcastAssistant == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+ return;
+ }
+
+ mServiceBroadcastAssistant.unregisterCallback(callback);
}
private void buildContentMetadata(String language, String programInfo) {
@@ -474,7 +584,7 @@
}
public boolean isProfileReady() {
- return mIsProfileReady;
+ return mIsBroadcastProfileReady;
}
@Override
@@ -494,40 +604,40 @@
* Not supported since LE Audio Broadcasts do not establish a connection.
*/
public int getConnectionStatus(BluetoothDevice device) {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
// LE Audio Broadcasts are not connection-oriented.
- return mService.getConnectionState(device);
+ return mServiceBroadcast.getConnectionState(device);
}
/**
* Not supported since LE Audio Broadcasts do not establish a connection.
*/
public List<BluetoothDevice> getConnectedDevices() {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
return new ArrayList<BluetoothDevice>(0);
}
// LE Audio Broadcasts are not connection-oriented.
- return mService.getConnectedDevices();
+ return mServiceBroadcast.getConnectedDevices();
}
public @NonNull
List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null.");
return Collections.emptyList();
}
- return mService.getAllBroadcastMetadata();
+ return mServiceBroadcast.getAllBroadcastMetadata();
}
public boolean isEnabled(BluetoothDevice device) {
- if (mService == null) {
+ if (mServiceBroadcast == null) {
return false;
}
- return !mService.getAllBroadcastMetadata().isEmpty();
+ return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty();
}
/**
@@ -571,12 +681,12 @@
if (DEBUG) {
Log.d(TAG, "finalize()");
}
- if (mService != null) {
+ if (mServiceBroadcast != null) {
try {
BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
BluetoothProfile.LE_AUDIO_BROADCAST,
- mService);
- mService = null;
+ mServiceBroadcast);
+ mServiceBroadcast = null;
} catch (Throwable t) {
Log.w(TAG, "Error cleaning up LeAudio proxy", t);
}
@@ -626,4 +736,21 @@
}
mContentResolver.unregisterContentObserver(mSettingsObserver);
}
+
+ private void stopLocalSourceReceivers() {
+ if (DEBUG) {
+ Log.d(TAG, "stopLocalSourceReceivers()");
+ }
+ for (BluetoothDevice device : mServiceBroadcastAssistant.getConnectedDevices()) {
+ for (BluetoothLeBroadcastReceiveState receiveState :
+ mServiceBroadcastAssistant.getAllSources(device)) {
+ /* Check if local/last broadcast is the synced one */
+ int localBroadcastId = getLatestBroadcastId();
+ if (receiveState.getBroadcastId() != localBroadcastId) continue;
+
+ mServiceBroadcastAssistant.removeSource(device, receiveState.getSourceId());
+ }
+ }
+ }
+
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 41ce58e..d1f7f2f 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 @@
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 e31a672..46e73d0 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 @@
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 d1d745f..7e97956 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 016dc21..9c9dd8a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -722,6 +722,7 @@
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 eaf0dcb..a945c33 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 @@
}
}
+ // 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 cb41eab..6817f53 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
@@ -19,6 +19,7 @@
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 @@
.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/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
index a902c5b..587395d 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -13,7 +13,7 @@
android:layout_height="@dimen/image_button_height"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
- android:scaleType="fitCenter"></ImageButton>
+ android:scaleType="fitCenter"/>
<TextView
android:id="@+id/shortcutLabel"
@@ -22,7 +22,6 @@
android:layout_marginTop="@dimen/grid_item_text_view_margin_top"
android:layout_below="@+id/shortcutIconBtn"
android:layout_centerHorizontal="true"
- android:ellipsize="end"
android:gravity="center_horizontal"
android:importantForAccessibility="no"
android:lines="2"
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 6d9497d..b9baa793 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.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 @@
.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 ab7bc26..ca7352e 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.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 @@
)
@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 130395a..29763c2 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.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 @@
.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 a213666..3da6a02 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 @@
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 0070552..774c409 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.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 @@
.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 49e2bf9..3dfdbba 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 @@
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()) }
- 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(),
- )
- }
- }
+ DisposableEffect(viewModel, state) {
+ viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() })
+ onDispose { viewModel.setTransitionState(null) }
}
-}
-/** 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,
- )
-
- 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)
- }
-
- if (backDestinationScene != null) {
- BackHandler { onSceneChanged.invoke(backDestinationScene) }
- }
- }
- }
-}
-
-@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,
+ SceneTransitionLayout(
+ currentScene = currentSceneKey.toTransitionSceneKey(),
+ onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) },
+ transitions = transitions {},
+ state = state,
+ modifier = modifier.fillMaxSize(),
) {
- Text(direction.name.lowercase(Locale.getDefault()))
+ 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(),
+ )
+ }
+ }
+ }
+ }
+}
+
+// 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,
+ )
+ }
+}
+
+// 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()
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+ return SceneTransitionSceneKey(
+ name = toString(),
+ identity = this,
+ )
+}
+
+// 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 b73e0b2..ff1cb5f 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.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 @@
)
@Composable
- override fun Content(
+ override fun SceneScope.Content(
modifier: Modifier,
) = ShadeScene(viewModel, modifier)
@@ -86,11 +87,12 @@
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/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
similarity index 65%
copy from packages/SystemUI/res/drawable/controls_list_divider_inset.xml
copy to packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
index ddfa18c..2fb7222 100644
--- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
+++ b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/controls_list_divider"
- android:insetRight="@dimen/control_spinner_padding_horizontal"
- android:insetLeft="@dimen/control_spinner_padding_horizontal" />
+<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-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index badad58..28b5870 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -192,7 +192,7 @@
<string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
<!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=70] -->
- <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
+ <string name="kg_prompt_unattended_update">Update will install when device not in use</string>
<!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
diff --git a/packages/SystemUI/res/drawable/controls_list_divider.xml b/packages/SystemUI/res/drawable/global_actions_list_divider.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/controls_list_divider.xml
rename to 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
similarity index 93%
rename from packages/SystemUI/res/drawable/controls_list_divider_inset.xml
rename to packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml
index ddfa18c..170bc0d 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 ecb0bfa..bea0e13 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 e1dbe69..f1939bb 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/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res/layout/controls_list_view.xml
similarity index 61%
copy from packages/SystemUI/res/drawable/controls_list_divider_inset.xml
copy to packages/SystemUI/res/layout/controls_list_view.xml
index ddfa18c..2831cbf 100644
--- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
+++ b/packages/SystemUI/res/layout/controls_list_view.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!--
+ Copyright (C) 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,8 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/controls_list_divider"
- android:insetRight="@dimen/control_spinner_padding_horizontal"
- android:insetLeft="@dimen/control_spinner_padding_horizontal" />
+<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 4cc461a..4923b05 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 b1259e4..c60fb26 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 8bff1a1..6de10b4 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 e1c2611..0000000
--- 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 ff3f0fb..3456515 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 1681f7a..0667cd8 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 2b1d9d6..7e892f7 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 de913ac..d74eca6 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 9f4fc39..e942258 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 de8287e..34f8f2d 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/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index bb11217..b81e081 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -178,6 +178,7 @@
getKeyguardSecurityCallback().dismiss(true, userId, getSecurityMode());
}
} else {
+ mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
if (isValidPassword) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
if (timeoutMs > 0) {
@@ -186,7 +187,6 @@
handleAttemptLockout(deadline);
}
}
- mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 3b09910f..aff2591 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,6 +27,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
@@ -1081,8 +1082,10 @@
mLockPatternUtils.reportFailedPasswordAttempt(userId);
if (timeoutMs > 0) {
mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
- mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
- mSecurityModel.getSecurityMode(userId));
+ if (!mFeatureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) {
+ mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
+ mSecurityModel.getSecurityMode(userId));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 48442a5..c03053d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2696,14 +2696,6 @@
}
/**
- * @return true if the FP sensor is non-UDFPS and the device can be unlocked using fingerprint
- * at this moment.
- */
- public boolean isFingerprintAllowedInBouncer() {
- return !isUdfpsSupported() && isUnlockingWithFingerprintAllowed();
- }
-
- /**
* @return true if there's at least one sfps enrollment for the current user.
*/
public boolean isSfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 6c8f8f3..4a31f3d 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 @@
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 @@
private boolean mAllowDiagonalScrolling = false;
private boolean mEditSizeEnable = false;
private boolean mSettingsPanelVisibility = false;
+ @VisibleForTesting
+ WindowMagnificationSizePrefs mWindowMagnificationSizePrefs;
@Nullable
private final MirrorWindowControl mMirrorWindowControl;
@@ -249,6 +251,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 0000000..4d7ad264
--- /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 e60d4e1..0c7d56f 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 @@
)
} 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 4b17be3..dc874d8 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 @@
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 ebff0b0..39a45f7 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 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.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 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 @@
@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 @@
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/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 86940ca..863ba8d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -82,7 +82,9 @@
) : LogContextInteractor {
init {
- foldProvider.start()
+ applicationScope.launch {
+ foldProvider.start()
+ }
}
override val displayState =
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 a3f34ce..eca0ada 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 @@
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 9bbf1ef..7b78761 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.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 @@
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 @@
// 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/bouncer/data/factory/BouncerMessageFactory.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
index 3206c00..1817ea9 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
@@ -32,6 +32,7 @@
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
@@ -53,6 +54,9 @@
import com.android.systemui.R.string.kg_primary_auth_locked_out_pattern
import com.android.systemui.R.string.kg_primary_auth_locked_out_pin
import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_update_password
+import com.android.systemui.R.string.kg_prompt_after_update_pattern
+import com.android.systemui.R.string.kg_prompt_after_update_pin
import com.android.systemui.R.string.kg_prompt_after_user_lockdown_password
import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pattern
import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
@@ -89,69 +93,76 @@
fun createFromPromptReason(
@BouncerPromptReason reason: Int,
userId: Int,
+ secondaryMsgOverride: String? = null
): BouncerMessageModel? {
val pair =
getBouncerMessage(
reason,
securityModel.getSecurityMode(userId),
- updateMonitor.isFingerprintAllowedInBouncer
+ updateMonitor.isUnlockingWithFingerprintAllowed
)
return pair?.let {
BouncerMessageModel(
- message = Message(messageResId = pair.first),
- secondaryMessage = Message(messageResId = pair.second)
+ message = Message(messageResId = pair.first, animate = false),
+ secondaryMessage =
+ secondaryMsgOverride?.let {
+ Message(message = secondaryMsgOverride, animate = false)
+ }
+ ?: Message(messageResId = pair.second, animate = false)
)
}
}
- fun createFromString(
- primaryMsg: String? = null,
- secondaryMsg: String? = null
- ): BouncerMessageModel =
- BouncerMessageModel(
- message = primaryMsg?.let { Message(message = it) },
- secondaryMessage = secondaryMsg?.let { Message(message = it) },
- )
-
/**
* Helper method that provides the relevant bouncer message that should be shown for different
- * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
+ * scenarios indicated by [reason]. [securityMode] & [fpAuthIsAllowed] parameters are used to
* provide a more specific message.
*/
private fun getBouncerMessage(
@BouncerPromptReason reason: Int,
securityMode: SecurityMode,
- fpAllowedInBouncer: Boolean = false
+ fpAuthIsAllowed: Boolean = false
): Pair<Int, Int>? {
return when (reason) {
+ // Primary auth locked out
+ PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
+ // Primary auth required reasons
PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
- PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
+ PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE -> authRequiredForMainlineUpdate(securityMode)
PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
- PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
- PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
- if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
- else incorrectSecurityInput(securityMode)
+ PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
+ // Non strong auth not available reasons
+ PROMPT_REASON_FACE_LOCKED_OUT ->
+ if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
+ else faceLockedOut(securityMode)
PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
- if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
+ if (fpAuthIsAllowed) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
else nonStrongAuthTimeout(securityMode)
PROMPT_REASON_TRUSTAGENT_EXPIRED ->
- if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
+ if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
else trustAgentDisabled(securityMode)
+ // Auth incorrect input reasons.
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
+ if (fpAuthIsAllowed) incorrectSecurityInputWithFingerprint(securityMode)
+ else incorrectSecurityInput(securityMode)
PROMPT_REASON_INCORRECT_FACE_INPUT ->
- if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
else incorrectFaceInput(securityMode)
PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
+ // Default message
PROMPT_REASON_DEFAULT ->
- if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
+ if (fpAuthIsAllowed) defaultMessageWithFingerprint(securityMode)
else defaultMessage(securityMode)
- PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
else -> null
}
}
+
+ fun emptyMessage(): BouncerMessageModel =
+ BouncerMessageModel(Message(message = ""), Message(message = ""))
}
@Retention(AnnotationRetention.SOURCE)
@@ -172,6 +183,7 @@
PROMPT_REASON_NONE,
PROMPT_REASON_RESTART,
PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+ PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE,
)
annotation class BouncerPromptReason
@@ -284,6 +296,15 @@
}
}
+private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): Pair<Int, Int> {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
+ else -> Pair(0, 0)
+ }
+}
+
private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
@@ -311,7 +332,7 @@
}
}
-private fun faceUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
+private fun faceLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
@@ -320,6 +341,15 @@
}
}
+private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): Pair<Int, Int> {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
+ SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
+ SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
+ else -> Pair(0, 0)
+ }
+}
+
private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
index 7e420cf..6fb0d4c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -28,6 +28,7 @@
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
@@ -38,6 +39,7 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
@@ -105,6 +107,9 @@
fun clearMessage()
}
+private const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+private const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+
@SysUISingleton
class BouncerMessageRepositoryImpl
@Inject
@@ -114,6 +119,7 @@
updateMonitor: KeyguardUpdateMonitor,
private val bouncerMessageFactory: BouncerMessageFactory,
private val userRepository: UserRepository,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
) : BouncerMessageRepository {
@@ -132,6 +138,9 @@
private val isAnyBiometricsEnabledAndEnrolled =
or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
+ private val wasRebootedForMainlineUpdate
+ get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
private val authFlagsBasedPromptReason: Flow<Int> =
combine(
biometricSettingsRepository.authenticationFlags,
@@ -144,7 +153,8 @@
return@map if (
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
) {
- PROMPT_REASON_RESTART
+ if (wasRebootedForMainlineUpdate) PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
+ else PROMPT_REASON_RESTART
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
PROMPT_REASON_TIMEOUT
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index d8cf398..8ed964d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -127,10 +127,12 @@
repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
sceneInteractor.setCurrentScene(
scene = SceneModel(SceneKey.Bouncer),
+ loggingReason = "request to unlock device while authentication required",
)
} else {
sceneInteractor.setCurrentScene(
scene = SceneModel(SceneKey.Gone),
+ loggingReason = "request to unlock device while authentication isn't required",
)
}
}
@@ -176,6 +178,7 @@
if (isAuthenticated) {
sceneInteractor.setCurrentScene(
scene = SceneModel(SceneKey.Gone),
+ loggingReason = "successful authentication",
)
} else {
repository.setMessage(errorMessage(getAuthenticationMethod()))
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index d06dd8e..fe01d08 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -86,7 +86,11 @@
repository.setFingerprintAcquisitionMessage(
if (value != null) {
- factory.createFromString(secondaryMsg = value)
+ factory.createFromPromptReason(
+ PROMPT_REASON_DEFAULT,
+ userRepository.getSelectedUserInfo().id,
+ secondaryMsgOverride = value
+ )
} else {
null
}
@@ -98,7 +102,11 @@
repository.setFaceAcquisitionMessage(
if (value != null) {
- factory.createFromString(secondaryMsg = value)
+ factory.createFromPromptReason(
+ PROMPT_REASON_DEFAULT,
+ userRepository.getSelectedUserInfo().id,
+ secondaryMsgOverride = value
+ )
} else {
null
}
@@ -110,7 +118,11 @@
repository.setCustomMessage(
if (value != null) {
- factory.createFromString(secondaryMsg = value)
+ factory.createFromPromptReason(
+ PROMPT_REASON_DEFAULT,
+ userRepository.getSelectedUserInfo().id,
+ secondaryMsgOverride = value
+ )
} else {
null
}
@@ -140,8 +152,7 @@
// always maps to an empty string.
private fun nullOrEmptyMessage() =
flowOf(
- if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
- else factory.createFromString("", "")
+ if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null else factory.emptyMessage()
)
val bouncerMessage =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
index 47fac2b..6486802 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
@@ -41,8 +41,6 @@
super.onFinishInflate()
primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
- primaryMessageView?.disable()
- secondaryMessageView?.disable()
}
fun init(factory: KeyguardMessageAreaController.Factory) {
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
index 9c3448b..dc32a59 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 @@
*/
@Override
public ViewHolder createView(ComplicationViewModel model) {
- return mDreamClockTimeViewHolderProvider.get();
+ return mComponentFactory.create().getViewHolder();
}
/**
@@ -94,11 +96,14 @@
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 @@
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 4d99282..7ac1cc7 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.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 @@
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 @@
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 0000000..17cc829
--- /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 0000000..87c3b2f
--- /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 fd711ee..0000000
--- 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 98975fbd..776c972 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 @@
* 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/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 5c2402b..4aef209 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 @@
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 @@
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 @@
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 0fb5b66..c9edd4a 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.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 @@
}
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 16bb75d..3cdf9ab 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.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
@@ -518,12 +517,10 @@
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,
- ))
- }
+ 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),
@@ -657,7 +654,7 @@
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 90b27c7..e471836 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 @@
@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")
@@ -266,7 +266,7 @@
@JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
// TODO(b/287268101): Tracking bug.
- @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock")
+ @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock", teamfood = true)
/** Migrate the lock icon view to the new keyguard root view. */
// TODO(b/286552209): Tracking bug.
@@ -617,7 +617,8 @@
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,11 +629,6 @@
// 2000 - device controls
@JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed")
- // 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 de67ba8..6d083f6 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 @@
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 2686233..66de371 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 @@
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 2085c87..888f746 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.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 @@
) {
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 c867c43..76d9893 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.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 @@
) {
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 44e1fd1..cca96b7 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 @@
},
)
+ /** 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/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index b5759e3..cc1504a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -496,4 +496,12 @@
public static LogBuffer provideDisplayMetricsRepoLogBuffer(LogBufferFactory factory) {
return factory.create("DisplayMetricsRepo", 50);
}
+
+ /** Provides a {@link LogBuffer} for the scene framework. */
+ @Provides
+ @SysUISingleton
+ @SceneFrameworkLog
+ public static LogBuffer provideSceneFrameworkLogBuffer(LogBufferFactory factory) {
+ return factory.create("SceneFramework", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SceneFrameworkLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/SceneFrameworkLog.kt
new file mode 100644
index 0000000..ef5f4e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SceneFrameworkLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for the Scene Framework. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SceneFrameworkLog
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 6d9844d..68202d5 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 @@
@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 @@
@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 @@
@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 @@
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/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index b4578e9..11a16a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -261,7 +261,10 @@
mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
mBroadcastAssistantCallback);
}
- connectBroadcastWithActiveDevice();
+ /* Add local source broadcast to connected capable devices that may be possible receivers
+ * of stream.
+ */
+ startBroadcastWithConnectedDevices();
}
@Override
@@ -394,30 +397,26 @@
}
}
- void connectBroadcastWithActiveDevice() {
+ void startBroadcastWithConnectedDevices() {
//get the Metadata, and convert to BT QR code format.
BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
if (broadcastMetadata == null) {
Log.e(TAG, "Error: There is no broadcastMetadata.");
return;
}
- MediaDevice mediaDevice = mMediaOutputController.getCurrentConnectedMediaDevice();
- if (mediaDevice == null || !(mediaDevice instanceof BluetoothMediaDevice)
- || !mediaDevice.isBLEDevice()) {
- Log.e(TAG, "Error: There is no active BT LE device.");
- return;
- }
- BluetoothDevice sink = ((BluetoothMediaDevice) mediaDevice).getCachedDevice().getDevice();
- Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
- + ", the device: " + sink.getAnonymizedAddress());
- if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
- Log.d(TAG, "The sink device has the broadcast source now.");
- return;
- }
- if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
- broadcastMetadata, /*isGroupOp=*/ true)) {
- Log.e(TAG, "Error: Source add failed");
+ for (BluetoothDevice sink : mMediaOutputController.getConnectedBroadcastSinkDevices()) {
+ Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+ + ", the device: " + sink.getAnonymizedAddress());
+
+ if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+ Log.d(TAG, "The sink device has the broadcast source now.");
+ return;
+ }
+ if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
+ broadcastMetadata, /*isGroupOp=*/ false)) {
+ Log.e(TAG, "Error: Source add failed");
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index be42569..d281f50 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -983,7 +983,7 @@
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
if (broadcast == null) {
- Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null");
+ Log.d(TAG, "getLocalBroadcastMetadataQrCodeString: LE Audio Broadcast is null");
return "";
}
final LocalBluetoothLeBroadcastMetadata metadata =
@@ -1087,11 +1087,23 @@
broadcast.unregisterServiceCallBack(callback);
}
+ List<BluetoothDevice> getConnectedBroadcastSinkDevices() {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "getConnectedBroadcastSinkDevices: The broadcast assistant profile is null");
+ return null;
+ }
+
+ return assistant.getConnectedDevices();
+ }
+
boolean isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink) {
LocalBluetoothLeBroadcastAssistant assistant =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
- Log.d(TAG, "The broadcast assistant profile is null");
+ Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: The broadcast assistant profile "
+ + "is null");
return false;
}
List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(sink);
@@ -1104,7 +1116,8 @@
LocalBluetoothLeBroadcastAssistant assistant =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
- Log.d(TAG, "The broadcast assistant profile is null");
+ Log.d(TAG, "addSourceIntoSinkDeviceWithBluetoothLeAssistant: The broadcast assistant "
+ + "profile is null");
return false;
}
assistant.addSource(sink, metadata, isGroupOp);
@@ -1117,7 +1130,8 @@
LocalBluetoothLeBroadcastAssistant assistant =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
- Log.d(TAG, "The broadcast assistant profile is null");
+ Log.d(TAG, "registerLeBroadcastAssistantServiceCallback: The broadcast assistant "
+ + "profile is null");
return;
}
Log.d(TAG, "Register LE broadcast assistant callback");
@@ -1129,7 +1143,8 @@
LocalBluetoothLeBroadcastAssistant assistant =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) {
- Log.d(TAG, "The broadcast assistant profile is null");
+ Log.d(TAG, "unregisterLeBroadcastAssistantServiceCallback: The broadcast assistant "
+ + "profile is null");
return;
}
Log.d(TAG, "Unregister LE broadcast assistant callback");
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 2adc211..0842fe0 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.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 @@
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 @@
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/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 03bd11b..9f45f66 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -220,7 +220,7 @@
// If scene framework is enabled, set the scene container window to
// visible and let the touch "slip" into that window.
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- mSceneInteractor.get().setVisible(true);
+ mSceneInteractor.get().setVisible(true, "swipe down on launcher");
} else {
centralSurfaces.onInputFocusTransfer(
mInputFocusTransferStarted, false /* cancel */,
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 1fca488..fee3960 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 @@
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 @@
_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 39daad3..64715bc 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,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.shared.logger.SceneLogger
+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
@@ -39,6 +42,7 @@
@Inject
constructor(
private val repository: SceneContainerRepository,
+ private val logger: SceneLogger,
) {
/**
@@ -52,8 +56,17 @@
}
/** Sets the scene in the container with the given name. */
- fun setCurrentScene(scene: SceneModel) {
+ fun setCurrentScene(scene: SceneModel, loggingReason: String) {
val currentSceneKey = repository.currentScene.value.key
+ if (currentSceneKey == scene.key) {
+ return
+ }
+
+ logger.logSceneChange(
+ from = currentSceneKey,
+ to = scene.key,
+ reason = loggingReason,
+ )
repository.setCurrentScene(scene)
repository.setSceneTransition(from = currentSceneKey, to = scene.key)
}
@@ -62,20 +75,34 @@
val currentScene: StateFlow<SceneModel> = repository.currentScene
/** Sets the visibility of the container with the given name. */
- fun setVisible(isVisible: Boolean) {
+ fun setVisible(isVisible: Boolean, loggingReason: String) {
+ val wasVisible = repository.isVisible.value
+ if (wasVisible == isVisible) {
+ return
+ }
+
+ logger.logVisibilityChange(
+ from = wasVisible,
+ to = isVisible,
+ reason = loggingReason,
+ )
return repository.setVisible(isVisible)
}
/** 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/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1c87eb2..20ee393 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -28,6 +28,7 @@
import com.android.systemui.model.SysUiState
import com.android.systemui.model.updateFlags
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
@@ -57,13 +58,17 @@
private val featureFlags: FeatureFlags,
private val sysUiState: SysUiState,
@DisplayId private val displayId: Int,
+ private val sceneLogger: SceneLogger,
) : CoreStartable {
override fun start() {
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+ sceneLogger.logFrameworkEnabled(isEnabled = true)
hydrateVisibility()
automaticallySwitchScenes()
hydrateSystemUiState()
+ } else {
+ sceneLogger.logFrameworkEnabled(isEnabled = false)
}
}
@@ -73,7 +78,9 @@
sceneInteractor.currentScene
.map { it.key }
.distinctUntilChanged()
- .collect { sceneKey -> sceneInteractor.setVisible(sceneKey != SceneKey.Gone) }
+ .collect { sceneKey ->
+ sceneInteractor.setVisible(sceneKey != SceneKey.Gone, "scene is $sceneKey")
+ }
}
}
@@ -88,10 +95,17 @@
isUnlocked ->
when (currentSceneKey) {
// When the device becomes unlocked in Bouncer, go to Gone.
- is SceneKey.Bouncer -> SceneKey.Gone
+ is SceneKey.Bouncer ->
+ SceneKey.Gone to "device unlocked in Bouncer scene"
// When the device becomes unlocked in Lockscreen, go to Gone if
// bypass is enabled.
- is SceneKey.Lockscreen -> SceneKey.Gone.takeIf { isBypassEnabled }
+ is SceneKey.Lockscreen ->
+ if (isBypassEnabled) {
+ SceneKey.Gone to
+ "device unlocked in Lockscreen scene with bypass"
+ } else {
+ null
+ }
// We got unlocked while on a scene that's not Lockscreen or
// Bouncer, no need to change scenes.
else -> null
@@ -104,13 +118,19 @@
is SceneKey.Bouncer -> null
// We got locked while on a scene that's not Lockscreen or Bouncer,
// go to Lockscreen.
- else -> SceneKey.Lockscreen
+ else ->
+ SceneKey.Lockscreen to "device locked in $currentSceneKey scene"
}
else -> null
}
}
.filterNotNull()
- .collect { targetSceneKey -> switchToScene(targetSceneKey) }
+ .collect { (targetSceneKey, loggingReason) ->
+ switchToScene(
+ targetSceneKey = targetSceneKey,
+ loggingReason = loggingReason,
+ )
+ }
}
applicationScope.launch {
@@ -121,7 +141,16 @@
if (isAsleep) {
// When the device goes to sleep, reset the current scene.
val isUnlocked = authenticationInteractor.isUnlocked.value
- switchToScene(if (isUnlocked) SceneKey.Gone else SceneKey.Lockscreen)
+ val (targetSceneKey, loggingReason) =
+ if (isUnlocked) {
+ SceneKey.Gone to "device is asleep while unlocked"
+ } else {
+ SceneKey.Lockscreen to "device is asleep while locked"
+ }
+ switchToScene(
+ targetSceneKey = targetSceneKey,
+ loggingReason = loggingReason,
+ )
}
}
}
@@ -147,9 +176,10 @@
}
}
- private fun switchToScene(targetSceneKey: SceneKey) {
+ private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.setCurrentScene(
scene = SceneModel(targetSceneKey),
+ loggingReason = loggingReason,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
new file mode 100644
index 0000000..0adbd5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.logger
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.SceneFrameworkLog
+import com.android.systemui.scene.shared.model.SceneKey
+import javax.inject.Inject
+
+class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) {
+
+ fun logFrameworkEnabled(isEnabled: Boolean) {
+ fun asWord(isEnabled: Boolean): String {
+ return if (isEnabled) "enabled" else "disabled"
+ }
+
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = { bool1 = isEnabled },
+ messagePrinter = { "Scene framework is ${asWord(bool1)}" }
+ )
+ }
+
+ fun logSceneChange(
+ from: SceneKey,
+ to: SceneKey,
+ reason: String,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = from.toString()
+ str2 = to.toString()
+ str3 = reason
+ },
+ messagePrinter = { "$str1 → $str2, reason: $str3" },
+ )
+ }
+
+ fun logVisibilityChange(
+ from: Boolean,
+ to: Boolean,
+ reason: String,
+ ) {
+ fun asWord(isVisible: Boolean): String {
+ return if (isVisible) "visible" else "invisible"
+ }
+
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {
+ str1 = asWord(from)
+ str2 = asWord(to)
+ str3 = reason
+ },
+ messagePrinter = { "$str1 → $str2, reason: $str3" },
+ )
+ }
+
+ companion object {
+ private const val TAG = "SceneFramework"
+ }
+}
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 0000000..9a30aa6
--- /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 f44748a..b4ebaec 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 @@
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. */
@@ -51,16 +53,27 @@
/** Requests a transition to the scene with the given key. */
fun setCurrentScene(scene: SceneModel) {
- interactor.setCurrentScene(scene)
+ interactor.setCurrentScene(
+ scene = scene,
+ loggingReason = SCENE_TRANSITION_LOGGING_REASON,
+ )
}
- /** 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. */
fun onRemoteUserInput(event: MotionEvent) {
interactor.onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
}
+
+ companion object {
+ private const val SCENE_TRANSITION_LOGGING_REASON = "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 2ea63c2..416f147 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.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 @@
mGoneToDreamingLockscreenHostedTransitionViewModel;
private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
+ private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
@@ -761,6 +763,7 @@
GoneToDreamingLockscreenHostedTransitionViewModel
goneToDreamingLockscreenHostedTransitionViewModel,
LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
+ PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
@@ -790,6 +793,7 @@
mGoneToDreamingLockscreenHostedTransitionViewModel =
goneToDreamingLockscreenHostedTransitionViewModel;
mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
+ mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mKeyguardInteractor = keyguardInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -1172,6 +1176,10 @@
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/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
new file mode 100644
index 0000000..8849d6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNotificationTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsNotificationTestCases"
+ }
+ ]
+}
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 80f5d19..a4e8c2e 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.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 @@
@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 @@
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 @@
FeatureFlags featureFlags,
PeopleNotificationIdentifier peopleNotificationIdentifier,
Optional<BubblesManager> bubblesManagerOptional,
- NotificationSettingsController settingsController,
ExpandableNotificationRowDragController dragController,
NotificationDismissibilityProvider dismissibilityProvider,
IStatusBarService statusBarService) {
@@ -255,7 +229,6 @@
mFeatureFlags = featureFlags;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
- mSettingsController = settingsController;
mDragController = dragController;
mMetricsLogger = metricsLogger;
mChildrenContainerLogger = childrenContainerLogger;
@@ -325,14 +298,12 @@
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 1657c28..0322573 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 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.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 @@
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 @@
}
}
- @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 @@
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 585ff52..0000000
--- 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/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 1227287..2affb817 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -150,10 +150,10 @@
fun onTouch(event: MotionEvent) {
if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) {
val upOrCancel =
- event.action == MotionEvent.ACTION_UP ||
+ event.action == MotionEvent.ACTION_UP ||
event.action == MotionEvent.ACTION_CANCEL
centralSurfaces.setInteracting(WINDOW_STATUS_BAR,
- !upOrCancel || shadeController.isExpandedVisible)
+ !upOrCancel || shadeController.isExpandedVisible)
}
}
@@ -171,7 +171,7 @@
if (!centralSurfaces.commandQueuePanelsEnabled) {
if (event.action == MotionEvent.ACTION_DOWN) {
Log.v(TAG, String.format("onTouchForwardedFromStatusBar: panel disabled, " +
- "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
+ "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
}
return false
}
@@ -182,7 +182,7 @@
sceneInteractor.get()
.onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
// TODO(b/291965119): remove once view is expanded to cover the status bar
- sceneInteractor.get().setVisible(true)
+ sceneInteractor.get().setVisible(true, "swipe down from status bar")
return false
}
@@ -191,11 +191,11 @@
// bar eat the gesture.
if (!shadeViewController.isViewEnabled) {
shadeLogger.logMotionEvent(event,
- "onTouchForwardedFromStatusBar: panel view disabled")
+ "onTouchForwardedFromStatusBar: panel view disabled")
return true
}
if (shadeViewController.isFullyCollapsed &&
- event.y < 1f) {
+ event.y < 1f) {
// b/235889526 Eat events on the top edge of the phone when collapsed
shadeLogger.logMotionEvent(event, "top edge touch ignored")
return true
@@ -257,27 +257,27 @@
view: PhoneStatusBarView
): PhoneStatusBarViewController {
val statusBarMoveFromCenterAnimationController =
- if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
- unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
- } else {
- null
- }
+ if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
+ unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
+ } else {
+ null
+ }
return PhoneStatusBarViewController(
- view,
- progressProvider.getOrNull(),
- centralSurfaces,
- shadeController,
- shadeViewController,
- sceneInteractor,
- shadeLogger,
- statusBarMoveFromCenterAnimationController,
- userChipViewModel,
- viewUtil,
- featureFlags,
- configurationController,
- statusOverlayHoverListenerFactory,
+ view,
+ progressProvider.getOrNull(),
+ centralSurfaces,
+ shadeController,
+ shadeViewController,
+ sceneInteractor,
+ shadeLogger,
+ statusBarMoveFromCenterAnimationController,
+ userChipViewModel,
+ viewUtil,
+ featureFlags,
+ configurationController,
+ statusOverlayHoverListenerFactory,
)
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index f4c5723..cffc833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -38,6 +38,19 @@
constructor(
@VerboseMobileViewLog private val buffer: LogBuffer,
) {
+ fun logBinderReceivedVisibility(parentView: View, subId: Int, visibility: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ bool1 = visibility
+ },
+ { "Binder[subId=$int1, viewId=$str1] received visibility: $bool1" },
+ )
+ }
+
fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index c221109..55bc8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -99,7 +99,16 @@
}
}
- launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
+ launch {
+ viewModel.isVisible.collect { isVisible ->
+ viewModel.verboseLogger?.logBinderReceivedVisibility(
+ view,
+ viewModel.subscriptionId,
+ isVisible
+ )
+ view.isVisible = isVisible
+ }
+ }
// Set the icon for the triangle
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5f0f7b8..0e97e21 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 @@
showRingerDrawer();
}
});
+ updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
mRingerDrawerVibrate.setOnClickListener(
new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE));
@@ -1007,6 +1008,19 @@
: 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 @@
.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 @@
.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 @@
});
}
- 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 @@
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 4da5d49..de9b5ee 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 @@
* 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 @@
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 b3ad9b0..75df1bd 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.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 @@
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
)
walletController.updateWalletPreference()
- walletController.queryWalletCards(callback)
+ walletController.queryWalletCards(callback, MAX_CARDS)
awaitClose {
walletController.unregisterWalletChangeObservers(
@@ -152,5 +151,6 @@
companion object {
private const val TAG = "WalletSuggestions"
+ private const val MAX_CARDS = 50
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index e447c29..efb981e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -733,20 +733,20 @@
// is
// not enough to trigger a dismissal of the keyguard.
underTest.onViewAttached()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
clearInvocations(viewMediatorCallback)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
@@ -754,12 +754,12 @@
// scene
// does not dismiss the keyguard while we're not listening.
underTest.onViewDetached()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// While not listening, moving back to the bouncer does not dismiss the keyguard.
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
@@ -767,7 +767,7 @@
// gone
// scene now does dismiss the keyguard again.
underTest.onViewAttached()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
}
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 11ad206..a48fa5d 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.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 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 @@
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 @@
}
@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 @@
mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
0);
}
+
@Test
- public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() {
+ 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);
});
- final int screenSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.magnification_max_frame_size) * 10;
+
+ WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+ assertTrue(params.width == windowFrameSize);
+ assertTrue(params.height == windowFrameSize);
+ }
+
+ @Test
+ public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ 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 @@
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 0000000..04b0d70
--- /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 b8bca3a..b0d0063 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 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 @@
private TestableLooper mLooper;
private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private BiometricNotificationService mBiometricNotificationService;
@Before
public void setUp() {
@@ -83,12 +85,10 @@
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 @@
.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 58982d1..7ab8e8b 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 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.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 @@
}
@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 @@
}
@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 @@
// 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/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
index 992ee1a..efae3fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
@@ -59,63 +59,79 @@
}
@Test
- fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
+ fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAuthIsAllowed() =
testScope.runTest {
- primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = false)
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAuthAllowed = false)
.isEqualTo("Enter PIN")
- primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = true)
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAuthAllowed = true)
.isEqualTo("Unlock with PIN or fingerprint")
- primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = false)
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAuthAllowed = false)
.isEqualTo("Enter password")
- primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = true)
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAuthAllowed = true)
.isEqualTo("Unlock with password or fingerprint")
- primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = false)
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAuthAllowed = false)
.isEqualTo("Draw pattern")
- primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = true)
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAuthAllowed = true)
.isEqualTo("Unlock with pattern or fingerprint")
}
@Test
- fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
+ fun bouncerMessages_overridesSecondaryMessageValue() =
+ testScope.runTest {
+ val bouncerMessageModel =
+ bouncerMessageModel(
+ PIN,
+ true,
+ PROMPT_REASON_DEFAULT,
+ secondaryMessageOverride = "face acquisition message"
+ )!!
+ assertThat(context.resources.getString(bouncerMessageModel.message!!.messageResId!!))
+ .isEqualTo("Unlock with PIN or fingerprint")
+ assertThat(bouncerMessageModel.secondaryMessage!!.message!!)
+ .isEqualTo("face acquisition message")
+ }
+
+ @Test
+ fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAuthIsAllowed() =
testScope.runTest {
primaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = PIN,
- fpAllowedInBouncer = true
+ fpAuthAllowed = true
)
.isEqualTo("Wrong PIN. Try again.")
secondaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = PIN,
- fpAllowedInBouncer = true
+ fpAuthAllowed = true
)
.isEqualTo("Or unlock with fingerprint")
primaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Password,
- fpAllowedInBouncer = true
+ fpAuthAllowed = true
)
.isEqualTo("Wrong password. Try again.")
secondaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Password,
- fpAllowedInBouncer = true
+ fpAuthAllowed = true
)
.isEqualTo("Or unlock with fingerprint")
primaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Pattern,
- fpAllowedInBouncer = true
+ fpAuthAllowed = true
)
.isEqualTo("Wrong pattern. Try again.")
secondaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Pattern,
- fpAllowedInBouncer = true
+ fpAuthAllowed = true
)
.isEqualTo("Or unlock with fingerprint")
}
@@ -123,11 +139,11 @@
private fun primaryMessage(
reason: Int,
mode: KeyguardSecurityModel.SecurityMode,
- fpAllowedInBouncer: Boolean
+ fpAuthAllowed: Boolean
): StringSubject {
return assertThat(
context.resources.getString(
- bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!.message!!.messageResId!!
+ bouncerMessageModel(mode, fpAuthAllowed, reason)!!.message!!.messageResId!!
)
)!!
}
@@ -135,25 +151,28 @@
private fun secondaryMessage(
reason: Int,
mode: KeyguardSecurityModel.SecurityMode,
- fpAllowedInBouncer: Boolean
+ fpAuthAllowed: Boolean
): StringSubject {
return assertThat(
context.resources.getString(
- bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!
- .secondaryMessage!!
- .messageResId!!
+ bouncerMessageModel(mode, fpAuthAllowed, reason)!!.secondaryMessage!!.messageResId!!
)
)!!
}
private fun bouncerMessageModel(
mode: KeyguardSecurityModel.SecurityMode,
- fpAllowedInBouncer: Boolean,
- reason: Int
+ fpAuthAllowed: Boolean,
+ reason: Int,
+ secondaryMessageOverride: String? = null,
): BouncerMessageModel? {
whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
- whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(fpAllowedInBouncer)
+ whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(fpAuthAllowed)
- return underTest.createFromPromptReason(reason, 0)
+ return underTest.createFromPromptReason(
+ reason,
+ 0,
+ secondaryMsgOverride = secondaryMessageOverride
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
index de712da..2be7d8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
@@ -79,6 +80,7 @@
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var securityModel: KeyguardSecurityModel
+ @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
@Captor
private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -99,7 +101,7 @@
fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
testScope = TestScope()
- whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
+ whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(false)
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
underTest =
BouncerMessageRepositoryImpl(
@@ -108,7 +110,8 @@
updateMonitor = updateMonitor,
bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
userRepository = userRepository,
- fingerprintAuthRepository = fingerprintRepository
+ fingerprintAuthRepository = fingerprintRepository,
+ systemPropertiesHelper = systemPropertiesHelper
)
}
@@ -214,6 +217,21 @@
}
@Test
+ fun onRestartForMainlineUpdate_shouldProvideRelevantMessage() =
+ testScope.runTest {
+ whenever(systemPropertiesHelper.get("sys.boot.reason.last"))
+ .thenReturn("reboot,mainline_update")
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ biometricSettingsRepository.setFaceEnrolled(true)
+ biometricSettingsRepository.setIsFaceAuthEnabled(true)
+
+ verifyMessagesForAuthFlag(
+ STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ Pair(keyguard_enter_pin, R.string.kg_prompt_after_update_pin),
+ )
+ }
+
+ @Test
fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
@@ -345,8 +363,8 @@
private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
return BouncerMessageModel(
- message = Message(messageResId = primaryResId),
- secondaryMessage = Message(messageResId = secondaryResId)
+ message = Message(messageResId = primaryResId, animate = false),
+ secondaryMessage = Message(messageResId = secondaryResId, animate = false)
)
}
private fun message(value: String): BouncerMessageModel {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index 8e5256e..3ca94aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -76,7 +76,7 @@
allowTestableLooperAsMainThread()
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
- whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
+ whenever(updateMonitor.isUnlockingWithFingerprintAllowed).thenReturn(false)
}
suspend fun TestScope.init() {
@@ -150,11 +150,12 @@
underTest.setCustomMessage("not empty")
- assertThat(repository.customMessage.value)
- .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+ val customMessage = repository.customMessage
+ assertThat(customMessage.value!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
+ assertThat(customMessage.value!!.secondaryMessage!!.message).isEqualTo("not empty")
underTest.setCustomMessage(null)
- assertThat(repository.customMessage.value).isNull()
+ assertThat(customMessage.value).isNull()
}
@Test
@@ -164,11 +165,15 @@
underTest.setFaceAcquisitionMessage("not empty")
- assertThat(repository.faceAcquisitionMessage.value)
- .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+ val faceAcquisitionMessage = repository.faceAcquisitionMessage
+
+ assertThat(faceAcquisitionMessage.value!!.message!!.messageResId)
+ .isEqualTo(keyguard_enter_pin)
+ assertThat(faceAcquisitionMessage.value!!.secondaryMessage!!.message)
+ .isEqualTo("not empty")
underTest.setFaceAcquisitionMessage(null)
- assertThat(repository.faceAcquisitionMessage.value).isNull()
+ assertThat(faceAcquisitionMessage.value).isNull()
}
@Test
@@ -178,11 +183,15 @@
underTest.setFingerprintAcquisitionMessage("not empty")
- assertThat(repository.fingerprintAcquisitionMessage.value)
- .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+ val fingerprintAcquisitionMessage = repository.fingerprintAcquisitionMessage
+
+ assertThat(fingerprintAcquisitionMessage.value!!.message!!.messageResId)
+ .isEqualTo(keyguard_enter_pin)
+ assertThat(fingerprintAcquisitionMessage.value!!.secondaryMessage!!.message)
+ .isEqualTo("not empty")
underTest.setFingerprintAcquisitionMessage(null)
- assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+ assertThat(fingerprintAcquisitionMessage.value).isNull()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 1f089ca..7f8d54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -79,7 +79,7 @@
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -99,7 +99,7 @@
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -119,7 +119,7 @@
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("password")
@@ -139,7 +139,7 @@
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -161,7 +161,7 @@
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index af54989..57fcbe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -83,7 +83,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -105,7 +105,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -128,7 +128,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -176,7 +176,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -208,7 +208,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index c12ed03..81c68ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -79,7 +79,7 @@
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -97,7 +97,7 @@
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -117,7 +117,7 @@
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -138,7 +138,7 @@
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -161,7 +161,7 @@
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -183,7 +183,7 @@
val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -203,7 +203,7 @@
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -227,7 +227,7 @@
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -258,7 +258,7 @@
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -277,7 +277,7 @@
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
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 57d3a01..cbbbe52 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 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 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 @@
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 @@
@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 @@
@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 @@
@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 63b0b25..2207180 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 @@
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/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 272f589..7ac1953 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 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 @@
@Mock private lateinit var userTracker: UserTracker
- private val featureFlags = FakeFeatureFlags()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -52,7 +48,6 @@
arrayOf<String>()
)
whenever(userTracker.userId).thenReturn(0)
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
}
@Test
@@ -132,25 +127,8 @@
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 0c7b9cb..6230ea7 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 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 @@
}
@Test
- fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
-
+ fun testShouldAddDefaultPanelDefaultsToTrue() {
assertThat(repository.shouldAddDefaultComponent()).isTrue()
}
@Test
- fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
- featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
-
- 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 a341ca3..8a1b094 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 @@
/**
* 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 daf5ce6..aa6bd4e 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 @@
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 @@
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/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index d825c2a..86e56bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -130,11 +130,11 @@
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen), "reason")
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
assertThat(isUnlocked).isFalse()
}
@@ -144,13 +144,13 @@
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
runCurrent()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
runCurrent()
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
assertThat(isUnlocked).isFalse()
}
@@ -161,7 +161,7 @@
val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
runCurrent()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason")
runCurrent()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
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 d8c78eb..904662e 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 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.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 @@
@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 @@
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 @@
assertThat(values.size).isEqualTo(3)
values.forEach { assertThat(it).isEqualTo(0f) }
+ }
- job.cancel()
+ @Test
+ fun lockscreenAlpha() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(2)
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ @Test
+ fun lockscreenAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.lockscreenAlpha)
+
+ 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 = mutableListOf<ScrimAlpha>()
-
- val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+ val values by collectValues(underTest.scrimAlpha)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
@@ -124,16 +153,12 @@
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 @@
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 @@
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/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 45e8e27..f25454c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -98,7 +98,8 @@
private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
private final MediaDevice mBluetoothMediaDevice = mock(BluetoothMediaDevice.class);
- private final BluetoothDevice mBluetoothDevice = mock(BluetoothDevice.class);
+ private final BluetoothDevice mBluetoothFirstDevice = mock(BluetoothDevice.class);
+ private final BluetoothDevice mBluetoothSecondDevice = mock(BluetoothDevice.class);
private final CachedBluetoothDevice mCachedBluetoothDevice = mock(CachedBluetoothDevice.class);
private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
@@ -142,20 +143,20 @@
}
@Test
- public void connectBroadcastWithActiveDevice_noBroadcastMetadata_failToAddSource() {
+ public void startBroadcastWithConnectedDevices_noBroadcastMetadata_failToAddSource() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(null);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
mLocalBluetoothLeBroadcastAssistant);
- mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+ mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
}
@Test
- public void connectBroadcastWithActiveDevice_noConnectedMediaDevice_failToAddSource() {
+ public void startBroadcastWithConnectedDevices_noConnectedMediaDevice_failToAddSource() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
@@ -164,13 +165,13 @@
mLocalBluetoothLeBroadcastAssistant);
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(null);
- mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+ mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
}
@Test
- public void connectBroadcastWithActiveDevice_hasBroadcastSource_failToAddSource() {
+ public void startBroadcastWithConnectedDevices_hasBroadcastSource_failToAddSource() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
@@ -180,19 +181,19 @@
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
.thenReturn(mCachedBluetoothDevice);
- when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
sourceList.add(mBluetoothLeBroadcastReceiveState);
- when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
sourceList);
- mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+ mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
}
@Test
- public void connectBroadcastWithActiveDevice_noBroadcastSource_failToAddSource() {
+ public void startBroadcastWithConnectedDevices_noBroadcastSource_failToAddSource() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
@@ -203,12 +204,16 @@
when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice()).thenReturn(
mCachedBluetoothDevice);
- when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
- when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
sourceList);
+ List<BluetoothDevice> connectedDevicesList = new ArrayList<>();
+ connectedDevicesList.add(mBluetoothFirstDevice);
+ when(mLocalBluetoothLeBroadcastAssistant.getConnectedDevices()).thenReturn(
+ connectedDevicesList);
- mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+ mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
}
@@ -360,4 +365,32 @@
assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
}
+
+ @Test
+ public void addSourceToAllConnectedDevices() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+ when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
+ when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
+ .thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
+ sourceList);
+ List<BluetoothDevice> connectedDevicesList = new ArrayList<>();
+ connectedDevicesList.add(mBluetoothFirstDevice);
+ connectedDevicesList.add(mBluetoothSecondDevice);
+ when(mLocalBluetoothLeBroadcastAssistant.getConnectedDevices()).thenReturn(
+ connectedDevicesList);
+
+ mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, times(2)).addSource(any(), any(), anyBoolean());
+ }
+
}
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 c65a2d3..d933b57 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 @@
// 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 @@
@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 826a6cc..56e3e96 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 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 @@
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 13a602d..4facc7a 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 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 @@
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() {
@@ -49,17 +52,26 @@
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test
fun sceneTransitionProgress() = runTest {
- val progress by collectLastValue(underTest.transitionProgress)
- assertThat(progress).isEqualTo(1f)
+ val transitionProgress by collectLastValue(underTest.transitionProgress)
+ assertThat(transitionProgress).isEqualTo(1f)
- underTest.setSceneTransitionProgress(0.55f)
- assertThat(progress).isEqualTo(0.55f)
+ val progress = MutableStateFlow(0.55f)
+ repository.setTransitionState(
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ ),
+ )
+ )
+ assertThat(transitionProgress).isEqualTo(0.55f)
}
@Test
@@ -67,10 +79,10 @@
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- underTest.setVisible(false)
+ underTest.setVisible(false, "reason")
assertThat(isVisible).isFalse()
- underTest.setVisible(true)
+ underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
@@ -80,7 +92,7 @@
assertThat(transitions).isNull()
val initialSceneKey = underTest.currentScene.value.key
- underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
assertThat(transitions)
.isEqualTo(
SceneTransitionModel(
@@ -89,7 +101,7 @@
)
)
- underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings))
+ underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason")
assertThat(transitions)
.isEqualTo(
SceneTransitionModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index b6bd31f..6be19b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -73,6 +73,7 @@
featureFlags = featureFlags,
sysUiState = sysUiState,
displayId = Display.DEFAULT_DISPLAY,
+ sceneLogger = mock(),
)
@Before
@@ -97,7 +98,7 @@
assertThat(isVisible).isFalse()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
assertThat(isVisible).isTrue()
}
@@ -117,10 +118,10 @@
underTest.start()
assertThat(isVisible).isTrue()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
assertThat(isVisible).isTrue()
- sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
assertThat(isVisible).isTrue()
}
@@ -326,7 +327,7 @@
SceneKey.QuickSettings,
)
.forEachIndexed { index, sceneKey ->
- sceneInteractor.setCurrentScene(SceneModel(sceneKey))
+ sceneInteractor.setCurrentScene(SceneModel(sceneKey), "reason")
runCurrent()
verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
@@ -342,7 +343,7 @@
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
authenticationRepository.setUnlocked(isDeviceUnlocked)
keyguardRepository.setBypassEnabled(isBypassEnabled)
- initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it)) }
+ initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") }
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 0ab98ad..9f3b12b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -52,10 +52,10 @@
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- interactor.setVisible(false)
+ interactor.setVisible(false, "reason")
assertThat(isVisible).isFalse()
- interactor.setVisible(true)
+ interactor.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
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 9bcc8aa..c3540cf 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 @@
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.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 @@
@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 @@
@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 @@
when(mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(anyInt()))
.thenReturn(emptyFlow());
+ // Primary Bouncer->Gone
+ when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
+ .thenReturn(emptyFlow());
+
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
mDumpManager,
@@ -511,7 +519,7 @@
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController,
- mock(NotificationWakeUpCoordinatorLogger.class));
+ new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()));
mConfigurationController = new ConfigurationControllerImpl(mContext);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
@@ -631,6 +639,7 @@
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 fe18fb5..b6da20f 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 @@
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.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 @@
// 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 @@
@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 @@
}
}
- 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 @@
.setSbn(mSbn)
.build();
mEntry.setRow(mRow);
-
- mAlertingNotificationManager = createAlertingNotificationManager(mTestHandler);
}
@After
@@ -180,68 +176,74 @@
@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 305f48b..764f7b6 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 @@
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 da3a9f6..78c0982 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.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 @@
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 9037df8..104b751 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.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.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 @@
@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 a869038..bfa03ee 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 @@
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 ac9a570..3dcfcfa 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 @@
@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 4143647..362da0b 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 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.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.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 @@
@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 @@
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 @@
@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 @@
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 ea70e9e..fbd61f4 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 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 @@
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 b5e77e0..548ecde 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 @@
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 @@
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 5793364..069eec2 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.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 @@
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 6eb391a..0b61a8d 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.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 @@
@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 ac254ab..bad56a3 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 @@
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 @@
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 6167b46..9a60272 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.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.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 @@
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 ca65987..04ffab3 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 @@
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 4d4d319..764005b8 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.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.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.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 @@
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 @@
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 @@
rivSubComponentFactory,
metricsLogger,
logBufferLogger,
- mock<NotificationChildrenContainerLogger>(),
+ NotificationChildrenContainerLogger(logcatLogBuffer()),
listContainer,
smartReplyConstants,
smartReplyController,
@@ -143,16 +134,11 @@
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 @@
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 @@
whenever(view.keepInParentForDismissAnimation()).thenReturn(true)
Assert.assertTrue(controller.removeFromParentIfKeptForAnimation())
- Mockito.verify(parentView).removeChildNotification(view)
+ verify(parentView).removeChildNotification(view)
}
@Test
@@ -202,9 +188,9 @@
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 @@
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 bdd82fd..cf5b3cd 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 @@
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 ba6c7fd..0b90ebe 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 @@
.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 @@
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 @@
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 2bccdca..0000000
--- 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 d21029d..1ab2b38 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.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 @@
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 @@
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 7c99568..32f0fe7 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 @@
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 07eadf7c..6f431be 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 @@
@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 6fda56c..72522ca 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 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 @@
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 @@
}
}
+ @Override
protected AlertingNotificationManager createAlertingNotificationManager() {
return mHeadsUpManager;
}
@Before
+ @Override
public void setUp() {
AccessibilityManagerWrapper accessibilityMgr =
mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
@@ -116,8 +122,10 @@
.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 @@
}
@After
+ @Override
public void tearDown() {
- mTestHandler.removeCallbacksAndMessages(null);
+ super.tearDown();
}
@Test
@@ -181,7 +190,6 @@
assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
}
-
@Test
public void testExtendHeadsUp() {
mHeadsUpManager.showNotification(mEntry);
@@ -189,7 +197,7 @@
() -> 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 33144f2..08a76ef 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 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.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 @@
mock(StatusBarRemoteInputCallback.class),
mActivityIntentHelper,
mock(MetricsLogger.class),
- mock(StatusBarNotificationActivityStarterLogger.class),
+ new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()),
mOnUserInteractionCallback,
mock(NotificationPresenter.class),
mock(ShadeViewController.class),
@@ -410,10 +412,12 @@
@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 @@
// 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 a797e03..14edf3d 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.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 @@
@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 @@
}
}
+ @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 @@
}
@After
+ @Override
public void tearDown() {
- mTestHandler.removeCallbacksAndMessages(null);
+ super.tearDown();
}
@Test
@@ -169,7 +176,7 @@
() -> 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/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index f299ad4..7593e84 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 @@
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.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 @@
@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/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index bf54d42..aa49287 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -20,6 +20,7 @@
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Handler
+import android.os.Looper
import android.testing.AndroidTestingRunner
import androidx.core.util.Consumer
import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.fail
import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
@@ -55,16 +57,20 @@
@Mock private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
- @Mock private lateinit var handler: Handler
-
@Mock private lateinit var rotationChangeProvider: RotationChangeProvider
@Mock private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
@Mock private lateinit var resources: Resources
+ @Mock private lateinit var handler: Handler
+
+ @Mock private lateinit var mainLooper: Looper
+
@Mock private lateinit var context: Context
+ @Mock private lateinit var thread: Thread
+
@Captor private lateinit var rotationListener: ArgumentCaptor<RotationListener>
private val foldProvider = TestFoldProvider()
@@ -89,6 +95,11 @@
override val halfFoldedTimeoutMillis: Int
get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
}
+ whenever(mainLooper.isCurrentThread).thenReturn(true)
+ whenever(handler.looper).thenReturn(mainLooper)
+ whenever(mainLooper.isCurrentThread).thenReturn(true)
+ whenever(mainLooper.thread).thenReturn(thread)
+ whenever(thread.name).thenReturn("backgroundThread")
whenever(context.resources).thenReturn(resources)
whenever(context.mainExecutor).thenReturn(mContext.mainExecutor)
@@ -435,6 +446,26 @@
}
@Test
+ fun startOnlyOnce_whenStartTriggeredThrice_startOnlyOnce() {
+ foldStateProvider.start()
+ foldStateProvider.start()
+ foldStateProvider.start()
+
+ assertThat(foldProvider.getNumberOfCallbacks()).isEqualTo(1)
+ }
+
+ @Test(expected = AssertionError::class)
+ fun startMethod_whileNotOnMainThread_throwsException() {
+ whenever(mainLooper.isCurrentThread).thenReturn(true)
+ try {
+ foldStateProvider.start()
+ fail("Should have thrown AssertionError: should be called from the main thread.")
+ } catch (e: AssertionError) {
+ assertThat(e.message).contains("backgroundThread")
+ }
+ }
+
+ @Test
fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() {
setKeyguardVisibility(visible = false)
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
@@ -658,6 +689,10 @@
fun notifyFolded(isFolded: Boolean) {
callbacks.forEach { it.onFoldUpdated(isFolded) }
}
+
+ fun getNumberOfCallbacks(): Int{
+ return callbacks.size
+ }
}
private class TestScreenOnStatusProvider : ScreenStatusProvider {
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 c819108..ee11cb6 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 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 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 @@
// 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 @@
}
}
+ 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 8e4f184..53e5e7d 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 @@
}
@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 3901d72..d5bdb59 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 @@
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 26a75d0..6cffb66 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
@@ -92,6 +92,7 @@
)
}
}
+
private val context = test.context
fun fakeSceneContainerRepository(
@@ -119,9 +120,12 @@
)
}
- fun sceneInteractor(): SceneInteractor {
+ fun sceneInteractor(
+ repository: SceneContainerRepository = fakeSceneContainerRepository()
+ ): SceneInteractor {
return SceneInteractor(
- repository = fakeSceneContainerRepository(),
+ repository = repository,
+ logger = mock(),
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 8c5244e..6743515 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -62,6 +62,7 @@
private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener()
private val foldStateListener = FoldStateListener()
+ private val mainLooper = handler.looper
private val timeoutRunnable = Runnable { cancelAnimation() }
private val rotationListener = RotationListener {
if (isTransitionInProgress) cancelAnimation()
@@ -77,22 +78,28 @@
private var isFolded = false
private var isScreenOn = false
private var isUnfoldHandled = true
+ private var isStarted = false
override fun start() {
+ assertMainThread()
+ if (isStarted) return
foldProvider.registerCallback(foldStateListener, mainExecutor)
screenStatusProvider.addCallback(screenListener)
hingeAngleProvider.addCallback(hingeAngleListener)
rotationChangeProvider.addCallback(rotationListener)
activityTypeProvider.init()
+ isStarted = true
}
override fun stop() {
+ assertMainThread()
screenStatusProvider.removeCallback(screenListener)
foldProvider.unregisterCallback(foldStateListener)
hingeAngleProvider.removeCallback(hingeAngleListener)
hingeAngleProvider.stop()
rotationChangeProvider.removeCallback(rotationListener)
activityTypeProvider.uninit()
+ isStarted = false
}
override fun addCallback(listener: FoldUpdatesListener) {
@@ -292,6 +299,14 @@
onHingeAngle(angle)
}
}
+
+ private fun assertMainThread() {
+ check(mainLooper.isCurrentThread) {
+ ("should be called from the main thread." +
+ " sMainLooper.threadName=" + mainLooper.thread.name +
+ " Thread.currentThread()=" + Thread.currentThread().name)
+ }
+ }
}
fun @receiver:FoldUpdate Int.name() =
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b76e99e..37abe1b 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 @@
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 5a9c470..c2d2468 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 @@
* 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 @@
* <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 @@
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 @@
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 @@
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 82d4d60..c5501f1 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.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 @@
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 @@
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;
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ 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;
+ }
+ }
+
+ 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 @@
+ " active=" + active
+ " deviceAddress=" + deviceAddress);
}
-
final int userId = getCallingUserId();
enforceCallerIsSystemOr(userId, packageName);
@@ -912,10 +954,17 @@
// 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 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 82628a6..928842c 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 @@
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 94cede8..d368b86 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 @@
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 @@
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 @@
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 0b2cce0..7e30790 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.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 @@
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 @@
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 @@
@NonNull AssociationStore associationStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mCallback = callback;
- mMainThreadHandler = new MainThreadHandler();
}
@MainThread
@@ -146,7 +129,7 @@
if (Looper.getMainLooper().isCurrentThread()) {
restartScan();
} else {
- mMainThreadHandler.post(this::restartScan);
+ new Handler(Looper.getMainLooper()).post(this::restartScan);
}
}
@@ -182,11 +165,10 @@
}
@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 @@
}
}
- private void stopScanIfNeeded() {
+ void stopScanIfNeeded() {
enforceInitialized();
if (DEBUG) Log.i(TAG, "stopScan()");
@@ -343,16 +325,11 @@
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 @@
}
};
- @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 a5410e4..6ba85bd 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 @@
}
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 f6e9415..f45a1c4 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.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 @@
* <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 @@
/** 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 @@
}
/**
+ * @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 @@
* {@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 @@
* {@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 @@
// 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 @@
}
}
- 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));
+ 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 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));
- }
-
- 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 @@
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 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 459e24d..9f263c8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -187,6 +187,7 @@
"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 f19f7f2..1741593 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -19,12 +19,10 @@
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.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 @@
*/
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 @@
private final UiEventLogger mUiEventLogger;
private boolean mHasFeatureWatch;
- private long mVibrateMilliSecondsForPanicGesture;
@VisibleForTesting
public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
@@ -268,13 +250,6 @@
mHasFeatureWatch =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- mVibrateMilliSecondsForPanicGesture =
- resources.getInteger(
- com.android
- .internal
- .R
- .integer
- .config_mashPressVibrateTimeOnPowerButton);
}
}
@@ -714,11 +689,6 @@
userSetupComplete));
}
- if (mHasFeatureWatch) {
- onEmergencyGestureDetectedOnWatch();
- return true;
- }
-
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onEmergencyActionLaunchGestureDetected();
@@ -728,37 +698,6 @@
}
}
- 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 dc83125..da2588b 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 @@
// 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 @@
} 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 @@
// 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 @@
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 @@
}
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 bef53c7..c20f0aa 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 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;
@@ -45,6 +46,7 @@
import android.util.KeyValueListParser;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import dalvik.annotation.optimization.NeverCompile;
@@ -153,6 +155,11 @@
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 +223,11 @@
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 +1063,19 @@
/** @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;
+
+ /**
+ * Indicates whether PSS profiling in AppProfiler is disabled or not.
+ */
+ static final String KEY_DISABLE_APP_PROFILER_PSS_PROFILING =
+ "disable_app_profiler_pss_profiling";
+
+ private final boolean mDefaultDisableAppProfilerPssProfiling;
+
+ public boolean APP_PROFILER_PSS_PROFILING_DISABLED;
+
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@Override
@@ -1226,6 +1251,9 @@
case KEY_USE_MODERN_TRIM:
updateUseModernTrim();
break;
+ case KEY_DISABLE_APP_PROFILER_PSS_PROFILING:
+ updateDisableAppProfilerPssProfiling();
+ break;
default:
updateFGSPermissionEnforcementFlagsIfNecessary(name);
break;
@@ -1307,7 +1335,10 @@
CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2;
CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
- rawMaxEmptyProcesses) / 3;
-
+ loadNativeBootDeviceConfigConstants();
+ mDefaultDisableAppProfilerPssProfiling = context.getResources().getBoolean(
+ R.bool.config_am_disablePssProfiling);
+ APP_PROFILER_PSS_PROFILING_DISABLED = mDefaultDisableAppProfilerPssProfiling;
}
public void start(ContentResolver resolver) {
@@ -1347,6 +1378,11 @@
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,9 +2033,22 @@
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);
+ .updatePermissionEnforcementFlagIfNecessary(name);
+ }
+
+ private void updateDisableAppProfilerPssProfiling() {
+ APP_PROFILER_PSS_PROFILING_DISABLED = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_DISABLE_APP_PROFILER_PSS_PROFILING,
+ mDefaultDisableAppProfilerPssProfiling);
}
@NeverCompile // Avoid size overhead of debugging code.
@@ -2187,6 +2236,12 @@
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.print(" "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING);
+ pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED);
+
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 a0fae26..c1f2f67 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 @@
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 @@
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 @@
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 @@
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
synchronized (mProcLock) {
+ mOomAdjuster.onProcessBeginLocked(app);
mOomAdjuster.setAttachingProcessStatesLSP(app);
clearProcessForegroundLocked(app);
app.setDebugging(false);
@@ -6980,6 +6986,7 @@
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/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 16f2226..e7f4bf9 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -633,16 +633,20 @@
return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID;
});
}
- final int numOfStats = stats.size();
- for (int j = 0; j < numOfStats; j++) {
- synchronized (mService.mPidsSelfLocked) {
- if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) {
- // This is one of our own processes; skip it.
- continue;
+
+ if (!mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED) {
+ final int numOfStats = stats.size();
+ for (int j = 0; j < numOfStats; j++) {
+ synchronized (mService.mPidsSelfLocked) {
+ if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) {
+ // This is one of our own processes; skip it.
+ continue;
+ }
}
+ nativeTotalPss += Debug.getPss(stats.get(j).pid, null, null);
}
- nativeTotalPss += Debug.getPss(stats.get(j).pid, null, null);
}
+
memInfo.readMemInfo();
synchronized (mService.mProcessStats.mLock) {
if (DEBUG_PSS) {
@@ -706,7 +710,8 @@
final boolean skipPSSCollection =
(profile.mApp.mOptRecord != null
&& profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen())
- || mService.isCameraActiveForUid(profile.mApp.uid);
+ || mService.isCameraActiveForUid(profile.mApp.uid)
+ || mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
long endTime = SystemClock.currentThreadTimeMillis();
synchronized (mProfilerLock) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 9026c20..b7fc484 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.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 @@
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 8c1fd51..2fff79b 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 @@
* 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 @@
* {@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 cc9628f..1a58556 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 @@
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 459c6ff..1f9e89e 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_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.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 @@
*/
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 @@
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 @@
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 @@
&& (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 @@
}
@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 @@
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 @@
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 @@
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 @@
* 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 @@
}
}
- // 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 @@
mNewNumAServiceProcs = 0;
}
+ // Reset state in all uid records.
+ resetUidRecordsLsp(activeUids);
+
boolean retryCycles = false;
boolean computeClients = fullUpdate || potentialCycles;
@@ -996,8 +1004,9 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
}
- 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 @@
}
}
+ 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 @@
continue;
}
- boolean trackedProcState = false;
+ computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll,
+ cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
- 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;
- }
-
- 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 @@
|| 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 (couldRecurse) {
+ // We're entering recursive functions below.
+ state.setCurRawAdj(adj);
+ state.setCurRawProcState(procState);
+ state.setCurrentSchedulingGroup(schedGroup);
+ state.setCurCapability(capability);
- if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
- continue;
- }
+ 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);
- 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;
- }
- }
-
- 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 @@
// 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 @@
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 @@
// 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 @@
/**
* @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 @@
/** 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 @@
/** 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 @@
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 @@
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 @@
}
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 16091d1..da5e12e 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 @@
* 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 0000000..b852ef5
--- /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 f532122c1..7037fec 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.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.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 @@
*/
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 @@
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 7ff6d11..a165e88 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 @@
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 @@
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 @@
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 @@
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 db341d2..a9c388c 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 @@
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 @@
@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 @@
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 @@
}
@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 @@
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 aabab61..f7bbc8b 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 @@
});
}
- 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 @@
}
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/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9db9e77..0e4465d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -87,6 +87,7 @@
DeviceConfig.NAMESPACE_CAMERA_NATIVE,
DeviceConfig.NAMESPACE_CONFIGURATION,
DeviceConfig.NAMESPACE_CONNECTIVITY,
+ DeviceConfig.NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL,
DeviceConfig.NAMESPACE_EDGETPU_NATIVE,
DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
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 0000000..683b3eb
--- /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 e4dd4a4..946f016 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.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.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 @@
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 @@
}
}
} break;
-
+ case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
+ onPersistAudioDeviceSettings();
+ break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -1980,6 +1985,8 @@
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 @@
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 b70e11d..f5b7ecf 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 @@
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 @@
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 @@
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 @@
}
for (DeviceInfo di : mConnectedDevices.values()) {
- if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+ if (!isBluetoothDevice(di.mDeviceType)) {
continue;
}
AudioDeviceAttributes ada =
@@ -1735,7 +1782,7 @@
}
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 @@
+ 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 @@
}
}
+ /*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 3ffca0e..3353b9e 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 @@
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 @@
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 @@
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 @@
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 @@
return mContentResolver;
}
+ /*package*/ SettingsAdapter getSettings() {
+ return mSettings;
+ }
+
///////////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////////
@@ -9258,10 +9262,6 @@
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 @@
}
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 462c938..969dd60 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.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 @@
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 @@
}
};
- 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 @@
/*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 @@
*/
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 @@
}
}
- // 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 @@
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 @@
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 @@
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,31 +540,50 @@
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.";
// Device logging is accomplished in the Java Audio Service level.
@@ -580,29 +591,30 @@
//
// 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 @@
* 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 @@
*/
@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 @@
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 @@
}
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 @@
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 @@
return;
}
if (mState == STATE_UNINITIALIZED) {
- init(true, null /* settings */);
+ init(true);
}
setSpatializerEnabledInt(true);
} else {
@@ -1137,16 +1150,16 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
//------------------------------------------------
// 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 f8f0088..df16c5b 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 @@
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 4f7a2ba..6191861 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 @@
@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 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 9ce6f8f..7c3ef19 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 @@
}
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 919fc71..c240bcb 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 @@
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 7452aab..009e097 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 @@
}
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 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 @@
}
if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid,
summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
- true)) {
+ true, false)) {
return summaryRecord;
}
}
@@ -6421,7 +6423,15 @@
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 @@
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 @@
* @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 @@
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 @@
* 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 @@
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 25ca1cb..ac52f9f 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 @@
user.unlockRealtime = SystemClock.elapsedRealtime();
}
}
+ if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM
+ && UserManager.isCommunalProfileEnabled()) {
+ mUms.startCommunalProfile();
+ }
}
@Override
@@ -828,6 +832,26 @@
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/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
new file mode 100644
index 0000000..1da9dd7
--- /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 0000000..d61bebc
--- /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/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 270f7f0c..2fef092 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -2296,12 +2296,13 @@
}
private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
- final int flags =
- commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0;
+ // This will bypass user settings, Do Not Disturb and other interruption policies.
+ final int flags = commonOptions.force ? ATTRIBUTES_ALL_BYPASS_FLAGS : 0;
return new VibrationAttributes.Builder()
.setFlags(flags)
- // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
- .setUsage(VibrationAttributes.USAGE_TOUCH)
+ // Used to allow vibrations when the adb shell process is running in background.
+ // This will apply the NOTIFICATION_VIBRATION_INTENSITY setting.
+ .setUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST)
.build();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2c866ab..8673b90 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 @@
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 @@
@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 d7667d8..4f3ab8b 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 @@
// 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 99cbdde..70edf3a 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 @@
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 @@
}
}
+ private static boolean isFoldable(Context context) {
+ return context.getResources().getIntArray(R.array.config_foldedDeviceStates).length > 0;
+ }
+
@VisibleForTesting
@Nullable
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
@@ -1469,11 +1472,6 @@
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 @@
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 @@
}
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 9c08c74..af770e2 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.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 @@
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 @@
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 @@
}
}
+ @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 @@
}
}
- 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 6d0fd2a..029f46f 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 @@
// 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 0000000..3271518
--- /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 00396e2..0bf8e71 100644
--- a/services/java/com/android/server/HsumBootUserInitializer.java
+++ b/services/java/com/android/server/HsumBootUserInitializer.java
@@ -63,13 +63,11 @@
/** 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 @@
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 @@
createMainUserIfNeeded();
t.traceEnd();
}
- if (mShouldAlwaysHaveCommunalProfile) {
- t.traceBegin("createCommunalProfileIfNeeded");
- createCommunalProfileIfNeeded();
- t.traceEnd();
- }
}
private void createMainUserIfNeeded() {
@@ -134,25 +126,6 @@
}
}
- 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 @@
}
}
- /**
- * 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 6a2d4dc..caa4343 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2781,14 +2781,26 @@
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 @@
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 c0cfa53..486ddb4 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 @@
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 6108ad2..ce4aa44 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 @@
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 1202c81..77e71ba 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 @@
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 5c75de8..299cc89 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 @@
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 @@
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 8c963aa..ff76a47 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 @@
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 @@
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 9868616..547e56c 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 @@
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 @@
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 b7d8b4c..7ed29e8 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 @@
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 @@
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 22fa8f2..160b227 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 @@
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 @@
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 9da3671..21f2af2 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 @@
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 @@
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 e39a3bb..171cfeb 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 @@
}
}
+ /**
+ * 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 d16c9c5..bf23117 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 @@
@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 @@
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 @@
int displayId1 =
localService.createVirtualDisplay(
builder1.build(),
- mMockAppToken /* callback */,
+ mMockAppToken2 /* callback */,
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
@@ -893,6 +896,7 @@
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 @@
int displayId2 =
localService.createVirtualDisplay(
builder2.build(),
- mMockAppToken /* callback */,
+ mMockAppToken2 /* callback */,
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
@@ -950,6 +954,8 @@
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 @@
int ownDisplayGroupDisplayId =
localService.createVirtualDisplay(
ownDisplayGroupConfig,
- mMockAppToken /* callback */,
+ mMockAppToken2 /* callback */,
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
@@ -1024,7 +1030,7 @@
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 e58ec45..01e49f2 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.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 @@
}
@Test
+ @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -356,6 +358,7 @@
}
@Test
+ @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -388,6 +391,7 @@
}
@Test
+ @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -418,6 +422,7 @@
}
@Test
+ @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
DisplayPowerControllerHolder followerDpc =
createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -450,6 +455,7 @@
}
@Test
+ @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowers_AutomaticBrightness() {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -521,6 +527,7 @@
}
@Test
+ @FlakyTest(bugId = 294107062)
public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
FOLLOWER_UNIQUE_ID);
@@ -612,6 +619,7 @@
}
@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 0000000..8bbacc4
--- /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 1f4563f..976e740 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.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 @@
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 @@
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 @@
ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
Collections.addAll(lru, apps);
+ for (ProcessRecord app : apps) {
+ sService.mOomAdjuster.onProcessBeginLocked(app);
+ }
}
/**
@@ -259,6 +270,7 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
{
@@ -1217,6 +1242,8 @@
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
{
@@ -1229,9 +1256,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
app.setPendingFinishAttach(true);
app.mState.setHasForegroundActivities(false);
+ sService.mOomAdjuster.onProcessBeginLocked(app);
sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
@@ -1991,7 +2025,9 @@
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 @@
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 @@
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 @@
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 31599ee..aba24fb 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.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 @@
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 @@
@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 @@
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 3ad24de..ad09ef0 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 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 @@
@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) { }
+ 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 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);
- }
-
- @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 @@
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 @@
* 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 @@
@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 @@
.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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6b225fc..bdee99b 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index d6c821f..8fadecd 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 @@
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.Parameters;
@Presubmit
-@LargeTest
+@MediumTest
@RunWith(JUnitParamsRunner.class)
public class ShortcutLoggingTests extends ShortcutKeyTestBase {
@@ -223,7 +222,7 @@
@Before
public void setUp() {
- setUpPhoneWindowManager();
+ setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
mPhoneWindowManager.overrideLaunchHome();
mPhoneWindowManager.overrideSearchKeyBehavior(
@@ -235,7 +234,6 @@
}
@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 7e7a9e1..ef3a6ed 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 @@
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 @@
.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 1c6408b..56c3ec0 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.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 @@
.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 @@
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 @@
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 @@
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 @@
@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 42422d9..939ff97 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 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 @@
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 @@
@Test
public void testSensorRotationAfterDisplayChangeBeforeTimeout_ignoresSensor() throws Exception {
- mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ mBuilder.setIsFoldable(true)
.setPauseRotationWhenUnfolding(true)
.setDisplaySwitchRotationBlockTimeMs(1000)
.build();
@@ -1035,7 +1063,7 @@
@Test
public void testSensorRotationAfterDisplayChangeAfterTimeout_usesSensor() throws Exception {
- mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ mBuilder.setIsFoldable(true)
.setPauseRotationWhenUnfolding(true)
.setDisplaySwitchRotationBlockTimeMs(1000)
.build();
@@ -1053,7 +1081,7 @@
@Test
public void testSensorRotationAfterHingeEventBeforeTimeout_ignoresSensor() throws Exception {
- mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ mBuilder.setIsFoldable(true)
.setPauseRotationWhenUnfolding(true)
.setMaxHingeAngle(165)
.setHingeAngleRotationBlockTimeMs(400)
@@ -1073,7 +1101,7 @@
@Test
public void testSensorRotationAfterHingeEventBeforeTimeoutFlagDisabled_usesSensorData()
throws Exception {
- mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ mBuilder.setIsFoldable(true)
.setPauseRotationWhenUnfolding(false)
.setMaxHingeAngle(165)
.setHingeAngleRotationBlockTimeMs(400)
@@ -1092,7 +1120,7 @@
@Test
public void testSensorRotationAfterHingeEventAfterTimeout_usesSensorData() throws Exception {
- mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ mBuilder.setIsFoldable(true)
.setPauseRotationWhenUnfolding(true)
.setMaxHingeAngle(165)
.setHingeAngleRotationBlockTimeMs(400)
@@ -1112,7 +1140,7 @@
@Test
public void testSensorRotationAfterLargeHingeEventBeforeTimeout_usesSensor() throws Exception {
- mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+ mBuilder.setIsFoldable(true)
.setPauseRotationWhenUnfolding(true)
.setMaxHingeAngle(165)
.setHingeAngleRotationBlockTimeMs(400)
@@ -1254,6 +1282,7 @@
private int mCarDockRotation;
private int mDeskDockRotation;
private int mUndockedHdmiRotation;
+ private boolean mIsFoldable;
private DisplayRotationBuilder setIsDefaultDisplay(boolean isDefaultDisplay) {
mIsDefaultDisplay = isDefaultDisplay;
@@ -1308,9 +1337,17 @@
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 @@
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 8bc4ced..db08eab 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 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 @@
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 e3d1b9c..6305bb6 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 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 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 @@
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 @@
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 @@
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 @@
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 040c5b0..a4ad0ce 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1319,8 +1319,11 @@
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 @@
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 1b5c537..26c17a4 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -399,6 +399,11 @@
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 @@
/** @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 @@
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 @@
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/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 1a77785..4032121 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -170,9 +170,7 @@
* the SF trace
*/
fun LegacyFlickerTest.navBarLayerPositionAtStart() {
- assertLayersStart {
- assertNavBarPosition(this, scenario.isGesturalNavigation)
- }
+ assertLayersStart { assertNavBarPosition(this, scenario.isGesturalNavigation) }
}
/**
@@ -180,9 +178,7 @@
* the SF trace
*/
fun LegacyFlickerTest.navBarLayerPositionAtEnd() {
- assertLayersEnd {
- assertNavBarPosition(this, scenario.isGesturalNavigation)
- }
+ assertLayersEnd { assertNavBarPosition(this, scenario.isGesturalNavigation) }
}
private fun assertNavBarPosition(sfState: LayerTraceEntrySubject, isGesturalNavigation: Boolean) {
@@ -195,19 +191,23 @@
when (navBarPosition) {
Position.TOP ->
- navBarRegion.hasSameTopPosition(displayArea)
+ navBarRegion
+ .hasSameTopPosition(displayArea)
.hasSameLeftPosition(displayArea)
.hasSameRightPosition(displayArea)
Position.BOTTOM ->
- navBarRegion.hasSameBottomPosition(displayArea)
+ navBarRegion
+ .hasSameBottomPosition(displayArea)
.hasSameLeftPosition(displayArea)
.hasSameRightPosition(displayArea)
Position.LEFT ->
- navBarRegion.hasSameLeftPosition(displayArea)
+ navBarRegion
+ .hasSameLeftPosition(displayArea)
.hasSameTopPosition(displayArea)
.hasSameBottomPosition(displayArea)
Position.RIGHT ->
- navBarRegion.hasSameRightPosition(displayArea)
+ navBarRegion
+ .hasSameRightPosition(displayArea)
.hasSameTopPosition(displayArea)
.hasSameBottomPosition(displayArea)
else -> error("Unknown position $navBarPosition")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
index e1af6e9..6209a08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
@@ -16,9 +16,9 @@
package com.android.server.wm.flicker.activityembedding
-import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.LegacyFlickerTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.Before
@@ -38,8 +38,6 @@
@Test
open fun backgroundLayerNeverVisible() {
val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
- flicker.assertLayers {
- isInvisible(backgroundColorLayer)
- }
+ flicker.assertLayers { isInvisible(backgroundColorLayer) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index d418407..c3529ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -23,8 +23,8 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,9 +35,8 @@
* Test changing split ratio at runtime on a horizona split.
*
* Setup: Launch A|B in horizontal split with B being the secondary activity, by default A and B
- * windows are equal in size. B is on the top and A is on the bottom.
- * Transitions:
- * Change the split ratio to A:B=0.7:0.3, expect bounds change for both A and B.
+ * windows are equal in size. B is on the top and A is on the bottom. Transitions: Change the split
+ * ratio to A:B=0.7:0.3, expect bounds change for both A and B.
*
* To run this test: `atest FlickerTests:HorizontalSplitChangeRatioTest`
*/
@@ -46,7 +45,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) :
- ActivityEmbeddingTestBase(flicker) {
+ ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
@@ -54,12 +53,9 @@
testApp.launchViaIntent(wmHelper)
testApp.launchSecondaryActivityHorizontally(wmHelper)
startDisplayBounds =
- wmHelper.currentState.layerState.physicalDisplayBounds
- ?: error("Display not found")
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
}
- transitions {
- testApp.changeSecondaryActivityRatio(wmHelper)
- }
+ transitions { testApp.changeSecondaryActivityRatio(wmHelper) }
teardown {
tapl.goHome()
testApp.exit(wmHelper)
@@ -94,7 +90,8 @@
@Test
fun secondaryActivityWindowIsAlwaysVisible() {
flicker.assertWm {
- isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) }
+ isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
}
/** Assert the Secondary activity window is always visible. */
@@ -110,15 +107,17 @@
fun secondaryActivityAdjustsHeightRuntime() {
flicker.assertLayersStart {
val topLayerRegion =
- this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
val bottomLayerRegion =
- this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(topLayerRegion.region.height).isEqual(bottomLayerRegion.region.height)
+ .that(topLayerRegion.region.height)
+ .isEqual(bottomLayerRegion.region.height)
check { "width" }
- .that(topLayerRegion.region.width).isEqual(bottomLayerRegion.region.width)
+ .that(topLayerRegion.region.width)
+ .isEqual(bottomLayerRegion.region.width)
topLayerRegion.notOverlaps(bottomLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
@@ -126,20 +125,20 @@
flicker.assertLayersEnd {
val topLayerRegion =
- this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
val bottomLayerRegion =
- this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(topLayerRegion.region.height).isLower(bottomLayerRegion.region.height)
+ .that(topLayerRegion.region.height)
+ .isLower(bottomLayerRegion.region.height)
check { "height" }
- .that(
- topLayerRegion.region.height / 0.3f -
- bottomLayerRegion.region.height / 0.7f)
- .isLower(0.1f)
+ .that(topLayerRegion.region.height / 0.3f - bottomLayerRegion.region.height / 0.7f)
+ .isLower(0.1f)
check { "width" }
- .that(topLayerRegion.region.width).isEqual(bottomLayerRegion.region.width)
+ .that(topLayerRegion.region.width)
+ .isEqual(bottomLayerRegion.region.width)
topLayerRegion.notOverlaps(bottomLayerRegion.region)
// Layers of two activities sum to be fullscreen size on display.
topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
@@ -159,4 +158,4 @@
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 845e649..244c5dc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -70,8 +70,7 @@
@Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
- @FlakyTest(bugId = 291575593)
- override fun entireScreenCovered() {}
+ @FlakyTest(bugId = 291575593) override fun entireScreenCovered() {}
@Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
index 404f329..4bb2246 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.Rect
-import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index 68b7e17..f409c4e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -104,8 +104,10 @@
flicker.assertWm {
notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
.then()
- .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT,
- isOptional = true)
+ .isAppWindowInvisible(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT,
+ isOptional = true
+ )
.then()
.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
}
@@ -133,8 +135,10 @@
}
// Begin of transition.
.then()
- .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT,
- isOptional = true)
+ .isAppWindowInvisible(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT,
+ isOptional = true
+ )
.then()
.invoke("mainAndSecondaryInSplit") {
val mainActivityRegion =
@@ -174,8 +178,10 @@
.coversExactly(startDisplayBounds)
}
.then()
- .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT,
- isOptional = true)
+ .isInvisible(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT,
+ isOptional = true
+ )
.then()
.isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 0417f9d..e4c35b2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -35,92 +35,84 @@
/**
* Test launching a secondary Activity into Picture-In-Picture mode.
*
- * Setup: Start from a split A|B.
- * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen.
+ * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom
+ * right corner on screen.
*
* To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest`
- *
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) :
- ActivityEmbeddingTestBase(flicker) {
+class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
override val transition: FlickerBuilder.() -> Unit = {
setup {
tapl.setExpectedRotationCheckEnabled(false)
testApp.launchViaIntent(wmHelper)
testApp.launchSecondaryActivity(wmHelper)
startDisplayBounds =
- wmHelper.currentState.layerState.physicalDisplayBounds
- ?: error("Can't get display bounds")
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Can't get display bounds")
}
- transitions {
- testApp.secondaryActivityEnterPip(wmHelper)
- }
+ transitions { testApp.secondaryActivityEnterPip(wmHelper) }
teardown {
tapl.goHome()
testApp.exit(wmHelper)
}
}
- /**
- * Main and secondary activity start from a split each taking half of the screen.
- */
+ /** Main and secondary activity start from a split each taking half of the screen. */
@Presubmit
@Test
fun layersStartFromEqualSplit() {
flicker.assertLayersStart {
- val leftLayerRegion =
- visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val leftLayerRegion = visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
val rightLayerRegion =
- visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
// Compare dimensions of two splits, given we're using default split attributes,
// both activities take up the same visible size on the display.
check { "height" }
- .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height)
+ .that(leftLayerRegion.region.height)
+ .isEqual(rightLayerRegion.region.height)
check { "width" }
- .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width)
+ .that(leftLayerRegion.region.width)
+ .isEqual(rightLayerRegion.region.width)
leftLayerRegion.notOverlaps(rightLayerRegion.region)
leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
}
flicker.assertLayersEnd {
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- .coversExactly(startDisplayBounds)
+ .coversExactly(startDisplayBounds)
}
}
- /**
- * Main Activity is visible throughout the transition and becomes fullscreen.
- */
+ /** Main Activity is visible throughout the transition and becomes fullscreen. */
@Presubmit
@Test
fun mainActivityWindowBecomesFullScreen() {
flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
flicker.assertWmEnd {
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- .coversExactly(startDisplayBounds)
+ .coversExactly(startDisplayBounds)
}
}
- /**
- * Main Activity is visible throughout the transition and becomes fullscreen.
- */
+ /** Main Activity is visible throughout the transition and becomes fullscreen. */
@Presubmit
@Test
fun mainActivityLayerBecomesFullScreen() {
flicker.assertLayers {
isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- .then()
- .isVisible(TRANSITION_SNAPSHOT)
- .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- .then()
- .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(TRANSITION_SNAPSHOT)
+ .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
}
flicker.assertLayersEnd {
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
- .coversExactly(startDisplayBounds)
+ .coversExactly(startDisplayBounds)
}
}
@@ -136,18 +128,15 @@
}
flicker.assertWmEnd {
val pipWindowRegion =
- visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- check{"height"}
- .that(pipWindowRegion.region.height)
- .isLower(startDisplayBounds.height / 2)
- check{"width"}
- .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ check { "height" }
+ .that(pipWindowRegion.region.height)
+ .isLower(startDisplayBounds.height / 2)
+ check { "width" }.that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
}
}
- /**
- * During the transition Secondary Activity shrinks to the bottom right corner.
- */
+ /** During the transition Secondary Activity shrinks to the bottom right corner. */
@Presubmit
@Test
fun secondaryLayerShrinks() {
@@ -162,13 +151,9 @@
}
}
flicker.assertLayersEnd {
- val pipRegion = visibleRegion(
- ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- check { "height" }
- .that(pipRegion.region.height)
- .isLower(startDisplayBounds.height / 2)
- check { "width" }
- .that(pipRegion.region.width).isLower(startDisplayBounds.width)
+ val pipRegion = visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ check { "height" }.that(pipRegion.region.height).isLower(startDisplayBounds.height / 2)
+ check { "width" }.that(pipRegion.region.width).isLower(startDisplayBounds.width)
}
}
@@ -185,4 +170,4 @@
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index f20e8e9..11e6bbe 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -56,9 +56,7 @@
launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_rtl_button")
}
- /**
- * Clicks the button to launch the secondary activity in a horizontal split.
- */
+ /** Clicks the button to launch the secondary activity in a horizontal split. */
fun launchSecondaryActivityHorizontally(wmHelper: WindowManagerStateHelper) {
launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_horizontally_button")
}
@@ -67,7 +65,7 @@
fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
val launchButton =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_third_activity_button")),
+ Until.findObject(By.res(packageName, "launch_third_activity_button")),
FIND_TIMEOUT
)
require(launchButton != null) { "Can't find launch third activity button on screen." }
@@ -87,7 +85,7 @@
fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
val launchButton =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
+ Until.findObject(By.res(packageName, "launch_trampoline_button")),
FIND_TIMEOUT
)
require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
@@ -105,53 +103,45 @@
*/
fun finishSecondaryActivity(wmHelper: WindowManagerStateHelper) {
val finishButton =
- uiDevice.wait(
- Until.findObject(By.res(getPackage(), "finish_secondary_activity_button")),
- FIND_TIMEOUT
- )
+ uiDevice.wait(
+ Until.findObject(By.res(packageName, "finish_secondary_activity_button")),
+ FIND_TIMEOUT
+ )
require(finishButton != null) { "Can't find finish secondary activity button on screen." }
finishButton.click()
wmHelper
- .StateSyncBuilder()
- .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
- .waitForAndVerify()
- }
+ .StateSyncBuilder()
+ .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
+ .waitForAndVerify()
+ }
- /**
- * Clicks the button to toggle the split ratio of secondary activity.
- */
+ /** Clicks the button to toggle the split ratio of secondary activity. */
fun changeSecondaryActivityRatio(wmHelper: WindowManagerStateHelper) {
val launchButton =
- uiDevice.wait(
- Until.findObject(
- By.res(getPackage(),
- "toggle_split_ratio_button")),
- FIND_TIMEOUT
- )
+ uiDevice.wait(
+ Until.findObject(By.res(packageName, "toggle_split_ratio_button")),
+ FIND_TIMEOUT
+ )
require(launchButton != null) {
"Can't find toggle ratio for secondary activity button on screen."
}
launchButton.click()
wmHelper
- .StateSyncBuilder()
- .withAppTransitionIdle()
- .withTransitionSnapshotGone()
- .waitForAndVerify()
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withTransitionSnapshotGone()
+ .waitForAndVerify()
}
fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
val pipButton =
- uiDevice.wait(
- Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")),
- FIND_TIMEOUT
- )
+ uiDevice.wait(
+ Until.findObject(By.res(packageName, "secondary_enter_pip_button")),
+ FIND_TIMEOUT
+ )
require(pipButton != null) { "Can't find enter pip button on screen." }
pipButton.click()
- wmHelper
- .StateSyncBuilder()
- .withAppTransitionIdle()
- .withPipShown()
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().withPipShown().waitForAndVerify()
}
/**
@@ -161,7 +151,7 @@
fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
val launchButton =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_always_expand_activity_button")),
+ Until.findObject(By.res(packageName, "launch_always_expand_activity_button")),
FIND_TIMEOUT
)
require(launchButton != null) {
@@ -171,8 +161,11 @@
wmHelper
.StateSyncBuilder()
.withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
- .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED,
- PlatformConsts.STATE_STOPPED)
+ .withActivityState(
+ MAIN_ACTIVITY_COMPONENT,
+ PlatformConsts.STATE_PAUSED,
+ PlatformConsts.STATE_STOPPED
+ )
.waitForAndVerify()
}
@@ -181,16 +174,16 @@
buttonName: String
) {
val launchButton =
- uiDevice.wait(Until.findObject(By.res(getPackage(), buttonName)), FIND_TIMEOUT)
+ uiDevice.wait(Until.findObject(By.res(packageName, buttonName)), FIND_TIMEOUT)
require(launchButton != null) {
"Can't find launch secondary activity button : " + buttonName + "on screen."
}
launchButton.click()
wmHelper
- .StateSyncBuilder()
- .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
- .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
- .waitForAndVerify()
+ .StateSyncBuilder()
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .waitForAndVerify()
}
/**
@@ -200,7 +193,7 @@
fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
val launchButton =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
+ Until.findObject(By.res(packageName, "launch_placeholder_split_button")),
FIND_TIMEOUT
)
require(launchButton != null) { "Can't find launch placeholder split button on screen." }
@@ -219,7 +212,7 @@
fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) {
val launchButton =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "launch_placeholder_split_rtl_button")),
+ Until.findObject(By.res(packageName, "launch_placeholder_split_rtl_button")),
FIND_TIMEOUT
)
require(launchButton != null) { "Can't find launch placeholder split button on screen." }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index 747cf37..3146139 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -41,7 +41,7 @@
*/
fun swipeDown(): Boolean {
val gameView =
- uiDevice.wait(Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS)
+ uiDevice.wait(Until.findObject(By.res(packageName, GAME_APP_VIEW_RES)), WAIT_TIME_MS)
require(gameView != null) { "Mock game app view not found." }
val bound = gameView.getVisibleBounds()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index d172252..252f7d3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -40,7 +40,7 @@
*/
open fun openIME(wmHelper: WindowManagerStateHelper) {
val editText =
- uiDevice.wait(Until.findObject(By.res(getPackage(), "plain_text_input")), FIND_TIMEOUT)
+ uiDevice.wait(Until.findObject(By.res(packageName, "plain_text_input")), FIND_TIMEOUT)
requireNotNull(editText) {
"Text field not found, this usually happens when the device " +
@@ -67,7 +67,7 @@
open fun finishActivity(wmHelper: WindowManagerStateHelper) {
val finishButton =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "finish_activity_btn")),
+ Until.findObject(By.res(packageName, "finish_activity_btn")),
FIND_TIMEOUT
)
requireNotNull(finishButton) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index 83a41ab..1a65611 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -71,7 +71,7 @@
if (rotation.isRotated()) {
imePackageName
} else {
- getPackage()
+ packageName
}
open(expectedPackage)
}
@@ -79,7 +79,7 @@
fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
val button =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "start_dialog_themed_activity_btn")),
+ Until.findObject(By.res(packageName, "start_dialog_themed_activity_btn")),
FIND_TIMEOUT
)
@@ -132,7 +132,7 @@
fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
val button =
uiDevice.wait(
- Until.findObject(By.res(getPackage(), "toggle_fixed_portrait_btn")),
+ Until.findObject(By.res(packageName, "toggle_fixed_portrait_btn")),
FIND_TIMEOUT
)
require(button != null) {
@@ -140,7 +140,7 @@
"was left in an unknown state (e.g. Screen turned off)"
}
button.click()
- mInstrumentation.waitForIdleSync()
+ instrumentation.waitForIdleSync()
// Ensure app relaunching transition finish and the IME has shown
waitIMEShown(wmHelper)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index d83b6d3..9b539c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -38,7 +38,7 @@
ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
) : StandardAppHelper(instr, launcherName, component) {
- private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation)
+ private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
fun clickRestart(wmHelper: WindowManagerStateHelper) {
val restartButton =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index c98f1c4..9895bda 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -37,7 +37,7 @@
fun openMail(rowIdx: Int) {
val rowSel =
- By.res(getPackage(), "mail_row_item_text").textEndsWith(String.format("%04d", rowIdx))
+ By.res(packageName, "mail_row_item_text").textEndsWith(String.format("%04d", rowIdx))
var row: UiObject2? = null
for (i in 1..1000) {
row = uiDevice.wait(Until.findObject(rowSel), SHORT_WAIT_TIME_MS)
@@ -46,7 +46,7 @@
}
require(row != null) { "" }
row.click()
- uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT)
+ uiDevice.wait(Until.gone(By.res(packageName, MAIL_LIST_RES_ID)), FIND_TIMEOUT)
}
fun scrollDown() {
@@ -55,7 +55,7 @@
}
fun waitForMailList(): UiObject2 {
- val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
+ val sel = By.res(packageName, MAIL_LIST_RES_ID).scrollable(true)
val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT)
requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" }
return ret
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 5b3d308..b2f8d47 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -36,7 +36,7 @@
) : StandardAppHelper(instr, launcherName, component) {
fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
val button =
- device.wait(Until.findObject(By.res(getPackage(), "launch_new_task")), FIND_TIMEOUT)
+ device.wait(Until.findObject(By.res(packageName, "launch_new_task")), FIND_TIMEOUT)
requireNotNull(button) {
"Button not found, this usually happens when the device " +
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index 7665690..e60c20d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -35,7 +35,7 @@
) : StandardAppHelper(instr, launcherName, component) {
fun postNotification(wmHelper: WindowManagerStateHelper) {
val button =
- uiDevice.wait(Until.findObject(By.res(getPackage(), "post_notification")), FIND_TIMEOUT)
+ uiDevice.wait(Until.findObject(By.res(packageName, "post_notification")), FIND_TIMEOUT)
requireNotNull(button) {
"Post notification button not found, this usually happens when the device " +
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 82de646..82d2ae0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -46,12 +46,14 @@
private val mediaController: MediaController?
get() =
- mediaSessionManager.getActiveSessions(null).firstOrNull { it.packageName == `package` }
+ mediaSessionManager.getActiveSessions(null).firstOrNull {
+ it.packageName == packageName
+ }
- private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation)
+ private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
open fun clickObject(resId: String) {
- val selector = By.res(`package`, resId)
+ val selector = By.res(packageName, resId)
val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
obj.click()
@@ -286,7 +288,7 @@
fun checkWithCustomActionsCheckbox() =
uiDevice
- .findObject(By.res(`package`, WITH_CUSTOM_ACTIONS_BUTTON_ID))
+ .findObject(By.res(packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
?.takeIf { it.isCheckable }
?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
?: error("'With custom actions' checkbox not found")
@@ -302,7 +304,7 @@
ReplaceWith("closePipWindow(wmHelper)")
)
open fun closePipWindow() {
- closePipWindow(WindowManagerStateHelper(mInstrumentation))
+ closePipWindow(WindowManagerStateHelper(instrumentation))
}
/** Returns the pip window bounds. */
@@ -386,8 +388,10 @@
Log.d(TAG, "window " + pipAppWindow)
if (pipAppWindow == null) return@add false
val pipRegion = pipAppWindow.frameRegion
- Log.d(TAG, "region " + pipRegion +
- " covers " + windowRect.coversMoreThan(pipRegion))
+ Log.d(
+ TAG,
+ "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion)
+ )
return@add windowRect.coversMoreThan(pipRegion)
}
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 895725c..8be5769 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -40,7 +40,7 @@
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
- val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
+ val launchActivityButton = By.res(packageName, LAUNCH_SECOND_ACTIVITY)
val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT)
requireNotNull(button) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 57eb172..3a784ff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -70,8 +70,8 @@
tapl
.goHome()
.switchToAllApps()
- .getAppIcon(testApp.launcherName)
- .launch(testApp.`package`)
+ .getAppIcon(testApp.appName)
+ .launch(testApp.packageName)
}
teardown { testApp.exit(wmHelper) }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 62fb570..4fc9bcb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -30,7 +30,7 @@
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.packageName) }
}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
index 687bc19..36e66c7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
@@ -46,7 +46,7 @@
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges("", testApp.packageName) }
}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt
index 00e75c7..b81439e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/Consts.kt
@@ -20,4 +20,4 @@
object Consts {
val IMAGE_WALLPAPER = ComponentNameMatcher("", "com.android.systemui.wallpapers.ImageWallpaper")
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index 0e33390..2aa444e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -67,7 +67,8 @@
wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
testApp.postNotification(wmHelper)
device.pressHome()
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withHomeActivityVisible()
.withWindowSurfaceDisappeared(ComponentNameMatcher.NOTIFICATION_SHADE)
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1987a68..bdbf0d2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -98,7 +98,7 @@
@Presubmit
@Test
fun focusChanges() {
- flicker.assertEventLog { this.focusChanges(testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges(testApp.packageName) }
}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 5b127c8..6d3ae43 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -116,8 +116,8 @@
flicker.assertWm {
this.invoke("isFullScreen") {
val appWindow =
- it.windowState(testApp.`package`)
- ?: error("App window for package ${testApp.`package`} not found")
+ it.windowState(testApp.packageName)
+ ?: error("App window for package ${testApp.packageName} not found")
val flags = appWindow.windowState.attributes.flags
appWindow
.check { "isFullScreen" }
@@ -134,8 +134,8 @@
flicker.assertWm {
this.invoke("isRotationSeamless") {
val appWindow =
- it.windowState(testApp.`package`)
- ?: error("App window for package ${testApp.`package`} not found")
+ it.windowState(testApp.packageName)
+ ?: error("App window for package ${testApp.packageName} not found")
val rotationAnimation = appWindow.windowState.attributes.rotationAnimation
appWindow
.check { "isRotationSeamless" }
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 7381a85..387dcfe 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -425,6 +425,7 @@
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 15fe11f..9452e58 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -46,6 +46,10 @@
"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 @@
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_;
};