Merge "restrict items in PowerMenu to a max height"
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index c967e51..45c6b8c 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -31,6 +31,9 @@
],
platform_apis: true,
test_suites: ["device-tests"],
- data: ["trace_configs/*"],
+ data: [
+ ":MultiUserPerfDummyApp",
+ "trace_configs/*",
+ ],
certificate: "platform",
}
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
index 81cec91..e84aea1 100644
--- a/apct-tests/perftests/packagemanager/Android.bp
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -33,7 +33,10 @@
test_suites: ["device-tests"],
- data: [":perfetto_artifacts"],
+ data: [
+ ":QueriesAll0",
+ ":perfetto_artifacts",
+ ],
certificate: "platform",
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 3fc87d3..ce381b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -22,7 +22,7 @@
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
import android.app.IActivityManager;
-import android.app.IUidObserver;
+import android.app.UidObserver;
import android.app.usage.UsageStatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -716,11 +716,7 @@
return true;
}
- private final class UidObserver extends IUidObserver.Stub {
- @Override
- public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- }
-
+ private final class UidObserver extends android.app.UidObserver {
@Override
public void onUidActive(int uid) {
mHandler.onUidActive(uid);
@@ -740,10 +736,6 @@
public void onUidCachedChanged(int uid, boolean cached) {
mHandler.onUidCachedChanged(uid, cached);
}
-
- @Override
- public void onUidProcAdjChanged(int uid) {
- }
}
private final class AppOpsWatcher extends IAppOpsCallback.Stub {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 70b06cb..887ee5f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1551,16 +1551,21 @@
jobStatus.getNumPreviousAttempts(),
jobStatus.getJob().getMaxExecutionDelayMillis(),
/* isDeadlineConstraintSatisfied */ false,
- /* isCharging */ false,
- /* batteryNotLow */ false,
- /* storageNotLow */false,
+ /* isChargingSatisfied */ false,
+ /* batteryNotLowSatisfied */ false,
+ /* storageNotLowSatisfied */false,
/* timingDelayConstraintSatisfied */ false,
- /* isDeviceIdle */ false,
+ /* isDeviceIdleSatisfied */ false,
/* hasConnectivityConstraintSatisfied */ false,
/* hasContentTriggerConstraintSatisfied */ false,
- 0,
+ /* jobStartLatencyMs */ 0,
jobStatus.getJob().isUserInitiated(),
- /* isRunningAsUserInitiatedJob */ false);
+ /* isRunningAsUserInitiatedJob */ false,
+ jobStatus.getJob().isPeriodic(),
+ jobStatus.getJob().getMinLatencyMillis(),
+ jobStatus.getEstimatedNetworkDownloadBytes(),
+ jobStatus.getEstimatedNetworkUploadBytes(),
+ jobStatus.getWorkCount());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1981,9 +1986,14 @@
cancelled.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
- 0,
+ /* jobStartLatencyMs */ 0,
cancelled.getJob().isUserInitiated(),
- /* isRunningAsUserInitiatedJob */ false);
+ /* isRunningAsUserInitiatedJob */ false,
+ cancelled.getJob().isPeriodic(),
+ cancelled.getJob().getMinLatencyMillis(),
+ cancelled.getEstimatedNetworkDownloadBytes(),
+ cancelled.getEstimatedNetworkUploadBytes(),
+ cancelled.getWorkCount());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 8355e9c..44700c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -471,7 +471,12 @@
job.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
mExecutionStartTimeElapsed - job.enqueueTime,
job.getJob().isUserInitiated(),
- job.shouldTreatAsUserInitiatedJob());
+ job.shouldTreatAsUserInitiatedJob(),
+ job.getJob().isPeriodic(),
+ job.getJob().getMinLatencyMillis(),
+ job.getEstimatedNetworkDownloadBytes(),
+ job.getEstimatedNetworkUploadBytes(),
+ job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
final String componentPackage = job.getServiceComponent().getPackageName();
@@ -1435,9 +1440,14 @@
completedJob.isConstraintSatisfied(JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE),
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY),
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_CONTENT_TRIGGER),
- 0,
+ mExecutionStartTimeElapsed - completedJob.enqueueTime,
completedJob.getJob().isUserInitiated(),
- completedJob.startedAsUserInitiatedJob);
+ completedJob.startedAsUserInitiatedJob,
+ completedJob.getJob().isPeriodic(),
+ completedJob.getJob().getMinLatencyMillis(),
+ completedJob.getEstimatedNetworkDownloadBytes(),
+ completedJob.getEstimatedNetworkUploadBytes(),
+ completedJob.getWorkCount());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 5d2c926..b9e3b76 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -682,7 +682,7 @@
static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
- private static final boolean DEFAULT_FLEXIBILITY_ENABLED = true;
+ private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
@VisibleForTesting
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 7cc2f28..6445c3b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -590,9 +590,10 @@
this.sourceTag = tag;
}
+ final String bnNamespace = namespace == null ? "" : "@" + namespace + "@";
this.batteryName = this.sourceTag != null
- ? this.sourceTag + ":" + job.getService().getPackageName()
- : job.getService().flattenToShortString();
+ ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName()
+ : bnNamespace + job.getService().flattenToShortString();
this.tag = "*job*/" + this.batteryName + "#" + job.getId();
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index aca0a6e..175c8d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -35,7 +35,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlarmManager;
-import android.app.IUidObserver;
+import android.app.UidObserver;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -382,31 +382,11 @@
}
};
- private class QcUidObserver extends IUidObserver.Stub {
+ private class QcUidObserver extends UidObserver {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
}
-
- @Override
- public void onUidGone(int uid, boolean disabled) {
- }
-
- @Override
- public void onUidActive(int uid) {
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- }
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override
- public void onUidProcAdjChanged(int uid) {
- }
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
index 3578c8a..58536675 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/ProcessStateModifier.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.IUidObserver;
+import android.app.UidObserver;
import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -61,7 +62,7 @@
@GuardedBy("mLock")
private final SparseIntArray mUidProcStateBucketCache = new SparseIntArray();
- private final IUidObserver mUidObserver = new IUidObserver.Stub() {
+ private final IUidObserver mUidObserver = new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
final int newBucket = getProcStateBucket(procState);
@@ -85,22 +86,6 @@
notifyStateChangedLocked(uid);
}
}
-
- @Override
- public void onUidActive(int uid) {
- }
-
- @Override
- public void onUidIdle(int uid, boolean disabled) {
- }
-
- @Override
- public void onUidCachedChanged(int uid, boolean cached) {
- }
-
- @Override
- public void onUidProcAdjChanged(int uid) {
- }
};
ProcessStateModifier(@NonNull InternalResourceService irs) {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b631586..b1f779f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -427,7 +427,7 @@
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
method public void expandNotificationsPanel();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getLastSystemKey();
- method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void handleSystemKey(int);
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void handleSystemKey(@NonNull android.view.KeyEvent);
method public void sendNotificationFeedback(@Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 29f774c..a6313db 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -46,6 +46,7 @@
import android.os.UserHandle;
import android.util.Pair;
import android.util.Slog;
+import android.view.KeyEvent;
import android.view.View;
import com.android.internal.statusbar.AppClipsServiceConnector;
@@ -740,7 +741,7 @@
*/
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
@TestApi
- public void handleSystemKey(int key) {
+ public void handleSystemKey(@NonNull KeyEvent key) {
try {
final IStatusBarService svc = getService();
if (svc != null) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 12882a2..9efdf28 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -39,21 +39,22 @@
import android.os.ResultReceiver;
/**
- * Interface for a virtual device.
+ * Interface for a virtual device for communication between the system server and the process of
+ * the owner of the virtual device.
*
* @hide
*/
interface IVirtualDevice {
/**
- * Returns the association ID for this virtual device.
+ * Returns the CDM association ID of this virtual device.
*
* @see AssociationInfo#getId()
*/
int getAssociationId();
/**
- * Returns the unique device ID for this virtual device.
+ * Returns the unique ID of this virtual device.
*/
int getDeviceId();
@@ -64,55 +65,99 @@
void close();
/**
- * Notifies of an audio session being started.
+ * Notifies that an audio session being started.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void onAudioSessionStarting(
- int displayId,
- IAudioRoutingCallback routingCallback,
+ void onAudioSessionStarting(int displayId, IAudioRoutingCallback routingCallback,
IAudioConfigChangedCallback configChangedCallback);
+ /**
+ * Notifies that an audio session has ended.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void onAudioSessionEnded();
+ /**
+ * Creates a new dpad and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualDpad(
- in VirtualDpadConfig config,
- IBinder token);
+ void createVirtualDpad(in VirtualDpadConfig config, IBinder token);
+
+ /**
+ * Creates a new keyboard and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualKeyboard(
- in VirtualKeyboardConfig config,
- IBinder token);
+ void createVirtualKeyboard(in VirtualKeyboardConfig config, IBinder token);
+
+ /**
+ * Creates a new mouse and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualMouse(
- in VirtualMouseConfig config,
- IBinder token);
+ void createVirtualMouse(in VirtualMouseConfig config, IBinder token);
+
+ /**
+ * Creates a new touchscreen and registers it with the input framework with the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualTouchscreen(
- in VirtualTouchscreenConfig config,
- IBinder token);
+ void createVirtualTouchscreen(in VirtualTouchscreenConfig config, IBinder token);
+
+ /**
+ * Creates a new navigation touchpad and registers it with the input framework with the given
+ * token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void createVirtualNavigationTouchpad(
- in VirtualNavigationTouchpadConfig config,
- IBinder token);
+ void createVirtualNavigationTouchpad(in VirtualNavigationTouchpadConfig config, IBinder token);
+
+ /**
+ * Removes the input device corresponding to the given token from the framework.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void unregisterInputDevice(IBinder token);
+
+ /**
+ * Returns the ID of the device corresponding to the given token, as registered with the input
+ * framework.
+ */
int getInputDeviceId(IBinder token);
+
+ /**
+ * Injects a key event to the virtual dpad corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
+
+ /**
+ * Injects a key event to the virtual keyboard corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+
+ /**
+ * Injects a button event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+
+ /**
+ * Injects a relative event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+
+ /**
+ * Injects a scroll event to the virtual mouse corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+
+ /**
+ * Injects a touch event to the virtual touch input device corresponding to the given token.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
- * Returns all virtual sensors for this device.
+ * Returns all virtual sensors created for this device.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
List<VirtualSensor> getVirtualSensorList();
@@ -126,8 +171,13 @@
/**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
- void launchPendingIntent(
- int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
+ void launchPendingIntent(int displayId, in PendingIntent pendingIntent,
+ in ResultReceiver resultReceiver);
+
+ /**
+ * Returns the current cursor position of the mouse corresponding to the given token, in x and y
+ * coordinates.
+ */
PointF getCursorPosition(IBinder token);
/** Sets whether to show or hide the cursor while this virtual device is active. */
@@ -140,8 +190,12 @@
* intent.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void registerIntentInterceptor(
- in IVirtualDeviceIntentInterceptor intentInterceptor, in IntentFilter filter);
+ void registerIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor,
+ in IntentFilter filter);
+
+ /**
+ * Unregisters a previously registered intent interceptor.
+ */
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 4f49b8d..07743cef5 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -101,7 +101,7 @@
*
* @param deviceId id of the virtual device.
* @param sound effect type corresponding to
- * {@code android.media.AudioManager.SystemSoundEffect}
+ * {@code android.media.AudioManager.SystemSoundEffect}
*/
void playSoundEffect(int deviceId, int effectType);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
index 91c209f..f284554 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
@@ -28,7 +28,7 @@
* Called when there's sound effect to be played on Virtual Device.
*
* @param sound effect type corresponding to
- * {@code android.media.AudioManager.SystemSoundEffect}
+ * {@code android.media.AudioManager.SystemSoundEffect}
*/
void onPlaySoundEffect(int effectType);
}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 4a09186..4ee65e0 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -26,6 +26,11 @@
/**
* Details of a particular virtual device.
+ *
+ * <p>Read-only device representation exposing the properties of an existing virtual device.
+ *
+ * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used
+ * by the virtual device creator and allows them to manage the device.
*/
public final class VirtualDevice implements Parcelable {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6b851a1..8b6377a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -68,7 +68,13 @@
import java.util.function.IntConsumer;
/**
- * System level service for managing virtual devices.
+ * System level service for creation and management of virtual devices.
+ *
+ * <p>VirtualDeviceManager enables interactive sharing of capabilities between the host Android
+ * device and a remote device.
+ *
+ * <p class="note">Not to be confused with the Android Studio's Virtual Device Manager, which allows
+ * for device emulation.
*/
@SystemService(Context.VIRTUAL_DEVICE_SERVICE)
public final class VirtualDeviceManager {
@@ -174,6 +180,9 @@
/**
* Returns the details of all available virtual devices.
+ *
+ * <p>The returned objects are read-only representations that expose the properties of all
+ * existing virtual devices.
*/
@NonNull
public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
@@ -252,11 +261,12 @@
*
* @param deviceId - id of the virtual audio device
* @return Device specific session id to be used for audio playback (see
- * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
- * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
- * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
- * is configured in context-aware mode.
- * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * {@link AudioManager#generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode. Otherwise
+ * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
+ *
* @hide
*/
public int getAudioPlaybackSessionId(int deviceId) {
@@ -275,11 +285,12 @@
*
* @param deviceId - id of the virtual audio device
* @return Device specific session id to be used for audio recording (see
- * {@link android.media.AudioManager.generateAudioSessionId}) if virtual device has
- * {@link VirtualDeviceParams.POLICY_TYPE_AUDIO} set to
- * {@link VirtualDeviceParams.DEVICE_POLICY_CUSTOM} and Virtual Audio Device
- * is configured in context-aware mode.
- * Otherwise {@link AUDIO_SESSION_ID_GENERATE} constant is returned.
+ * {@link AudioManager#generateAudioSessionId}) if virtual device has
+ * {@link VirtualDeviceParams#POLICY_TYPE_AUDIO} set to
+ * {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM} and Virtual Audio Device
+ * is configured in context-aware mode. Otherwise
+ * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} constant is returned.
+ *
* @hide
*/
public int getAudioRecordingSessionId(int deviceId) {
@@ -296,10 +307,11 @@
/**
* Requests sound effect to be played on virtual device.
*
- * @see android.media.AudioManager#playSoundEffect(int)
+ * @see AudioManager#playSoundEffect(int)
*
* @param deviceId - id of the virtual audio device
* @param effectType the type of sound effect
+ *
* @hide
*/
public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
@@ -315,11 +327,18 @@
}
/**
- * A virtual device has its own virtual display, audio output, microphone, sensors, etc. The
- * creator of a virtual device can take the output from the virtual display and stream it over
- * to another device, and inject input events that are received from the remote device.
+ * A representation of a virtual device.
*
- * TODO(b/204081582): Consider using a builder pattern for the input APIs.
+ * <p>A virtual device can have its own virtual displays, audio input/output, sensors, etc.
+ * The creator of a virtual device can take the output from the virtual display and stream it
+ * over to another device, and inject input and sensor events that are received from the remote
+ * device.
+ *
+ * <p>This object is only used by the virtual device creator and allows them to manage the
+ * device's behavior, peripherals, and the user interaction with that device.
+ *
+ * <p class="note">Not to be confused with {@link android.companion.virtual.VirtualDevice},
+ * which is a read-only representation exposing the properties of an existing virtual device.
*
* @hide
*/
@@ -346,8 +365,10 @@
}
/**
- * @return A new Context bound to this device. This is a convenience method equivalent to
- * calling {@link Context#createDeviceContext(int)} with the device id of this device.
+ * Returns a new context bound to this device.
+ *
+ * <p>This is a convenience method equivalent to calling
+ * {@link Context#createDeviceContext(int)} with the id of this device.
*/
public @NonNull Context createContext() {
return mVirtualDeviceInternal.createContext();
@@ -400,20 +421,19 @@
* @param height The height of the virtual display in pixels, must be greater than 0.
* @param densityDpi The density of the virtual display in dpi, must be greater than 0.
* @param surface The surface to which the content of the virtual display should
- * be rendered, or null if there is none initially. The surface can also be set later using
- * {@link VirtualDisplay#setSurface(Surface)}.
+ * be rendered, or null if there is none initially. The surface can also be set later
+ * using {@link VirtualDisplay#setSurface(Surface)}.
* @param flags A combination of virtual display flags accepted by
- * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
- * automatically set for all virtual devices:
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
- * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
- * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+ * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
+ * automatically set for all virtual devices:
+ * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC} and
+ * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
* @param executor The executor on which {@code callback} will be invoked. This is ignored
- * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
- * not be null.
+ * if {@code callback} is {@code null}. If {@code callback} is specified, this executor
+ * must not be null.
* @param callback Callback to call when the state of the {@link VirtualDisplay} changes
* @return The newly created virtual display, or {@code null} if the application could
- * not create the virtual display.
+ * not create the virtual display.
*
* @see DisplayManager#createVirtualDisplay
*
@@ -450,11 +470,11 @@
*
* @param config The configuration of the display.
* @param executor The executor on which {@code callback} will be invoked. This is ignored
- * if {@code callback} is {@code null}. If {@code callback} is specified, this executor must
- * not be null.
+ * if {@code callback} is {@code null}. If {@code callback} is specified, this executor
+ * must not be null.
* @param callback Callback to call when the state of the {@link VirtualDisplay} changes
* @return The newly created virtual display, or {@code null} if the application could
- * not create the virtual display.
+ * not create the virtual display.
*
* @see DisplayManager#createVirtualDisplay
*/
@@ -478,7 +498,7 @@
/**
* Creates a virtual dpad.
*
- * @param config the configurations of the virtual Dpad.
+ * @param config the configurations of the virtual dpad.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
@@ -500,11 +520,10 @@
/**
* Creates a virtual keyboard.
*
- * @param display the display that the events inputted through this device should
- * target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param display the display that the events inputted through this device should target.
+ * @param inputDeviceName the name of this keyboard device.
+ * @param vendorId the PCI vendor id.
+ * @param productId the product id, as defined by the vendor.
* @see #createVirtualKeyboard(VirtualKeyboardConfig config)
* @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
*/
@@ -537,14 +556,12 @@
/**
* Creates a virtual mouse.
*
- * @param display the display that the events inputted through this device should
- * target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param display the display that the events inputted through this device should target.
+ * @param inputDeviceName the name of this mouse.
+ * @param vendorId the PCI vendor id.
+ * @param productId the product id, as defined by the vendor.
* @see #createVirtualMouse(VirtualMouseConfig config)
* @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
- * *
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -576,11 +593,10 @@
/**
* Creates a virtual touchscreen.
*
- * @param display the display that the events inputted through this device should
- * target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param display the display that the events inputted through this device should target.
+ * @param inputDeviceName the name of this touchscreen device.
+ * @param vendorId the PCI vendor id.
+ * @param productId the product id, as defined by the vendor.
* @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
* @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
* instead
@@ -605,11 +621,13 @@
/**
* Creates a virtual touchpad in navigation mode.
*
- * A touchpad in navigation mode means that its events are interpreted as navigation events
- * (up, down, etc) instead of using them to update a cursor's absolute position. If the
- * events are not consumed they are converted to DPAD events.
+ * <p>A touchpad in navigation mode means that its events are interpreted as navigation
+ * events (up, down, etc) instead of using them to update a cursor's absolute position. If
+ * the events are not consumed they are converted to DPAD events and delivered to the target
+ * again.
*
* @param config the configurations of the virtual navigation touchpad.
+ * @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
@@ -629,10 +647,10 @@
*
* @param display The target virtual display to capture from and inject into.
* @param executor The {@link Executor} object for the thread on which to execute
- * the callback. If <code>null</code>, the {@link Executor} associated with
- * the main {@link Looper} will be used.
+ * the callback. If <code>null</code>, the {@link Executor} associated with the main
+ * {@link Looper} will be used.
* @param callback Interface to be notified when playback or recording configuration of
- * applications running on virtual display is changed.
+ * applications running on virtual display is changed.
* @return A {@link VirtualAudioDevice} instance.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -648,7 +666,7 @@
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
- * visibility is true.
+ * visibility is true.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setShowPointerIcon(boolean showPointerIcon) {
@@ -669,8 +687,7 @@
}
/**
- * Removes an activity listener previously added with
- * {@link #addActivityListener}.
+ * Removes an activity listener previously added with {@link #addActivityListener}.
*
* @param listener The listener to remove.
* @see #addActivityListener(Executor, ActivityListener)
@@ -692,10 +709,10 @@
}
/**
- * Removes a sound effect listener previously added with {@link #addActivityListener}.
+ * Removes a sound effect listener previously added with {@link #addSoundEffectListener}.
*
* @param soundEffectListener The listener to remove.
- * @see #addActivityListener(Executor, ActivityListener)
+ * @see #addSoundEffectListener(Executor, SoundEffectListener)
*/
public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
mVirtualDeviceInternal.removeSoundEffectListener(soundEffectListener);
@@ -722,7 +739,7 @@
}
/**
- * Unregisters the intent interceptorCallback previously registered with
+ * Unregisters the intent interceptor previously registered with
* {@link #registerIntentInterceptor}.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -760,9 +777,9 @@
* {@link #onDisplayEmpty(int)} will be called. If the value topActivity is cached, it
* should be cleared when {@link #onDisplayEmpty(int)} is called.
*
- * @param displayId The display ID on which the activity change happened.
+ * @param displayId The display ID on which the activity change happened.
* @param topActivity The component name of the top activity.
- * @param userId The user ID associated with the top activity.
+ * @param userId The user ID associated with the top activity.
*/
default void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
@UserIdInt int userId) {}
@@ -799,6 +816,7 @@
/**
* Listener for system sound effect playback on virtual device.
+ *
* @hide
*/
@SystemApi
@@ -807,8 +825,8 @@
/**
* Called when there's a system sound effect to be played on virtual device.
*
- * @param effectType - system sound effect type, see
- * {@code android.media.AudioManager.SystemSoundEffect}
+ * @param effectType - system sound effect type
+ * @see android.media.AudioManager.SystemSoundEffect
*/
void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 9a34dbe..45d6dc6 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -34,6 +34,7 @@
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorDirectChannelCallback;
import android.content.ComponentName;
+import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SharedMemory;
@@ -680,7 +681,7 @@
* {@link #NAVIGATION_POLICY_DEFAULT_ALLOWED}, meaning activities are allowed to launch
* unless they are in {@code blockedCrossTaskNavigations}.
*
- * <p> This method must not be called if {@link #setAllowedCrossTaskNavigations(Set)} has
+ * <p>This method must not be called if {@link #setAllowedCrossTaskNavigations(Set)} has
* been called.
*
* @throws IllegalArgumentException if {@link #setAllowedCrossTaskNavigations(Set)} has
@@ -847,11 +848,11 @@
* <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
* otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
* the playback session id is set to value other than
- * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+ * {@link android.media.AudioManager#AUDIO_SESSION_ID_GENERATE}.
*
* @param playbackSessionId requested device-specific audio session id for playback
- * @see android.media.AudioManager.generateAudioSessionId()
- * @see android.media.AudioTrack.Builder.setContext(Context)
+ * @see android.media.AudioManager#generateAudioSessionId()
+ * @see android.media.AudioTrack.Builder#setContext(Context)
*/
@NonNull
public Builder setAudioPlaybackSessionId(int playbackSessionId) {
@@ -871,11 +872,11 @@
* <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
* otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
* the recording session id is set to value other than
- * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+ * {@link android.media.AudioManager#AUDIO_SESSION_ID_GENERATE}.
*
* @param recordingSessionId requested device-specific audio session id for playback
- * @see android.media.AudioManager.generateAudioSessionId()
- * @see android.media.AudioRecord.Builder.setContext(Context)
+ * @see android.media.AudioManager#generateAudioSessionId()
+ * @see android.media.AudioRecord.Builder#setContext(Context)
*/
@NonNull
public Builder setAudioRecordingSessionId(int recordingSessionId) {
diff --git a/core/java/android/companion/virtual/audio/AudioCapture.java b/core/java/android/companion/virtual/audio/AudioCapture.java
index d6d0d2b..dd5e660 100644
--- a/core/java/android/companion/virtual/audio/AudioCapture.java
+++ b/core/java/android/companion/virtual/audio/AudioCapture.java
@@ -56,12 +56,12 @@
/**
* Sets the {@link AudioRecord} to handle audio capturing.
- * Callers may call this multiple times with different audio records to change
- * the underlying {@link AudioRecord} without stopping and re-starting recording.
*
- * @param audioRecord The underlying {@link AudioRecord} to use for capture,
- * or null if no audio (i.e. silence) should be captured while still keeping the
- * record in a recording state.
+ * <p>Callers may call this multiple times with different audio records to change the underlying
+ * {@link AudioRecord} without stopping and re-starting recording.
+ *
+ * @param audioRecord The underlying {@link AudioRecord} to use for capture, or null if no audio
+ * (i.e. silence) should be captured while still keeping the record in a recording state.
*/
void setAudioRecord(@Nullable AudioRecord audioRecord) {
Log.d(TAG, "set AudioRecord with " + audioRecord);
diff --git a/core/java/android/companion/virtual/audio/AudioInjection.java b/core/java/android/companion/virtual/audio/AudioInjection.java
index 9d6a3eb..5de5f7e 100644
--- a/core/java/android/companion/virtual/audio/AudioInjection.java
+++ b/core/java/android/companion/virtual/audio/AudioInjection.java
@@ -65,12 +65,12 @@
/**
* Sets the {@link AudioTrack} to handle audio injection.
- * Callers may call this multiple times with different audio tracks to change
- * the underlying {@link AudioTrack} without stopping and re-starting injection.
*
- * @param audioTrack The underlying {@link AudioTrack} to use for injection,
- * or null if no audio (i.e. silence) should be injected while still keeping the
- * record in a playing state.
+ * <p>Callers may call this multiple times with different audio tracks to change the underlying
+ * {@link AudioTrack} without stopping and re-starting injection.
+ *
+ * @param audioTrack The underlying {@link AudioTrack} to use for injection, or null if no audio
+ * (i.e. silence) should be injected while still keeping the record in a playing state.
*/
void setAudioTrack(@Nullable AudioTrack audioTrack) {
Log.d(TAG, "set AudioTrack with " + audioTrack);
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
index 3cb0572..dcdb6c6 100644
--- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl
@@ -33,7 +33,7 @@
* @param enabled Whether the sensor is enabled.
* @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
* @param batchReportingLatencyMicros The requested maximum time interval in microseconds
- * between the delivery of two batches of sensor events.
+ * between the delivery of two batches of sensor events.
*/
void onConfigurationChanged(in VirtualSensor sensor, boolean enabled, int samplingPeriodMicros,
int batchReportLatencyMicros);
@@ -60,7 +60,7 @@
* @param sensor The sensor, for which the channel was configured.
* @param rateLevel The rate level used to configure the direct sensor channel.
* @param reportToken A positive sensor report token, used to differentiate between events from
- * different sensors within the same channel.
+ * different sensors within the same channel.
*/
void onDirectChannelConfigured(int channelHandle, in VirtualSensor sensor, int rateLevel,
int reportToken);
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index bda44d4..eaa1792 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -30,7 +30,7 @@
* Representation of a sensor on a remote device, capable of sending events, such as an
* accelerometer or a gyroscope.
*
- * This registers the sensor device with the sensor framework as a runtime sensor.
+ * <p>A virtual sensor device is registered with the sensor framework as a runtime sensor.
*
* @hide
*/
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
index e6bd6da..4d586f6 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java
@@ -45,10 +45,10 @@
*
* @param sensor The sensor whose requested injection parameters have changed.
* @param enabled Whether the sensor is enabled. True if any listeners are currently registered,
- * and false otherwise.
+ * and false otherwise.
* @param samplingPeriod The requested sampling period of the sensor.
* @param batchReportLatency The requested maximum time interval between the delivery of two
- * batches of sensor events.
+ * batches of sensor events.
*/
void onConfigurationChanged(@NonNull VirtualSensor sensor, boolean enabled,
@NonNull Duration samplingPeriod, @NonNull Duration batchReportLatency);
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index ef55ca9..3bdf9aa 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -31,7 +31,9 @@
/**
* Configuration for creation of a virtual sensor.
+ *
* @see VirtualSensor
+ *
* @hide
*/
@SystemApi
@@ -122,6 +124,7 @@
/**
* Returns the vendor string of the sensor.
+ *
* @see Builder#setVendor
*/
@Nullable
@@ -130,7 +133,8 @@
}
/**
- * Returns maximum range of the sensor in the sensor's unit.
+ * Returns the maximum range of the sensor in the sensor's unit.
+ *
* @see Sensor#getMaximumRange
*/
public float getMaximumRange() {
@@ -138,7 +142,8 @@
}
/**
- * Returns The resolution of the sensor in the sensor's unit.
+ * Returns the resolution of the sensor in the sensor's unit.
+ *
* @see Sensor#getResolution
*/
public float getResolution() {
@@ -146,7 +151,8 @@
}
/**
- * Returns The power in mA used by this sensor while in use.
+ * Returns the power in mA used by this sensor while in use.
+ *
* @see Sensor#getPower
*/
public float getPower() {
@@ -154,8 +160,9 @@
}
/**
- * Returns The minimum delay allowed between two events in microseconds, or zero depending on
+ * Returns the minimum delay allowed between two events in microseconds, or zero depending on
* the sensor type.
+ *
* @see Sensor#getMinDelay
*/
public int getMinDelay() {
@@ -163,7 +170,8 @@
}
/**
- * Returns The maximum delay between two sensor events in microseconds.
+ * Returns the maximum delay between two sensor events in microseconds.
+ *
* @see Sensor#getMaxDelay
*/
public int getMaxDelay() {
@@ -201,6 +209,7 @@
/**
* Returns the sensor flags.
+ *
* @hide
*/
public int getFlags() {
@@ -233,7 +242,7 @@
*
* @param type The type of the sensor, matching {@link Sensor#getType}.
* @param name The name of the sensor. Must be unique among all sensors with the same type
- * that belong to the same virtual device.
+ * that belong to the same virtual device.
*/
public Builder(@IntRange(from = 1) int type, @NonNull String name) {
if (type <= 0) {
@@ -275,6 +284,7 @@
/**
* Sets the maximum range of the sensor in the sensor's unit.
+ *
* @see Sensor#getMaximumRange
*/
@NonNull
@@ -285,6 +295,7 @@
/**
* Sets the resolution of the sensor in the sensor's unit.
+ *
* @see Sensor#getResolution
*/
@NonNull
@@ -295,6 +306,7 @@
/**
* Sets the power in mA used by this sensor while in use.
+ *
* @see Sensor#getPower
*/
@NonNull
@@ -305,6 +317,7 @@
/**
* Sets the minimum delay allowed between two events in microseconds.
+ *
* @see Sensor#getMinDelay
*/
@NonNull
@@ -315,6 +328,7 @@
/**
* Sets the maximum delay between two sensor events in microseconds.
+ *
* @see Sensor#getMaxDelay
*/
@NonNull
@@ -339,11 +353,11 @@
* Sets whether direct sensor channel of the given types is supported.
*
* @param memoryTypes A combination of {@link SensorDirectChannel.MemoryType} flags
- * indicating the types of shared memory supported for creating direct channels. Only
- * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for virtual
- * sensors.
+ * indicating the types of shared memory supported for creating direct channels. Only
+ * {@link SensorDirectChannel#TYPE_MEMORY_FILE} direct channels may be supported for
+ * virtual sensors.
* @throws IllegalArgumentException if {@link SensorDirectChannel#TYPE_HARDWARE_BUFFER} is
- * set to be supported.
+ * set to be supported.
*/
@NonNull
public VirtualSensorConfig.Builder setDirectChannelTypesSupported(
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
index d352f94f..f10e9d0 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java
@@ -45,6 +45,8 @@
* <p>The callback is tied to the VirtualDevice's lifetime as the virtual sensors are created when
* the device is created and destroyed when the device is destroyed.
*
+ * @see VirtualSensorDirectChannelWriter
+ *
* @hide
*/
@SystemApi
@@ -94,7 +96,7 @@
* @param sensor The sensor, for which the channel was configured.
* @param rateLevel The rate level used to configure the direct sensor channel.
* @param reportToken A positive sensor report token, used to differentiate between events from
- * different sensors within the same channel.
+ * different sensors within the same channel.
*
* @see VirtualSensorConfig.Builder#setHighestDirectReportRateLevel(int)
* @see VirtualSensorConfig.Builder#setDirectChannelTypesSupported(int)
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
index 6aed96f..bf78dd0 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java
@@ -41,6 +41,41 @@
* write the events from the relevant sensors directly to the shared memory regions of the
* corresponding {@link SensorDirectChannel} instances.
*
+ * <p>Example:
+ * <p>During sensor and virtual device creation:
+ * <pre>
+ * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter();
+ * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() {
+ * @Override
+ * public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) {
+ * writer.addChannel(channelHandle, sharedMemory);
+ * }
+ * @Override
+ * public void onDirectChannelDestroyed(int channelHandle);
+ * writer.removeChannel(channelHandle);
+ * }
+ * @Override
+ * public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel,
+ * int reportToken)
+ * if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) {
+ * // handle error
+ * }
+ * }
+ * }
+ * </pre>
+ * <p>During the virtual device lifetime:
+ * <pre>
+ * VirtualSensor sensor = ...
+ * while (shouldInjectEvents(sensor)) {
+ * if (!writer.writeSensorEvent(sensor, event)) {
+ * // handle error
+ * }
+ * }
+ * writer.close();
+ * </pre>
+ * <p>Note that the virtual device owner should take the currently configured rate level into
+ * account when deciding whether and how often to inject events for a particular sensor.
+ *
* @see android.hardware.SensorDirectChannel#configure
* @see VirtualSensorDirectChannelCallback
*
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
index 01b4975..a368467e 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -121,7 +121,7 @@
* monotonically increasing using the same time base as
* {@link android.os.SystemClock#elapsedRealtimeNanos()}.
*
- * If not explicitly set, the current timestamp is used for the sensor event.
+ * <p>If not explicitly set, the current timestamp is used for the sensor event.
*
* @see android.hardware.SensorEvent#timestamp
*/
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 6ff4271..f946754 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -531,8 +531,9 @@
mInstantAppVisibility = o.mInstantAppVisibility;
}
- /** {@inheritDoc} */
- public String toString() {
+ /** @hide */
+ public String toLongString() {
+ // Not implemented directly as toString() due to potential memory regression
final StringBuilder sb = new StringBuilder();
sb.append("IntentFilter {");
sb.append(" pri=");
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
index 6bd9de4..d4c249e 100644
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -40,24 +40,50 @@
@NonNull
private final IBinder mToken;
+ private final boolean mShouldShowCancellationUi;
+
+ @NonNull
+ private final String mAppPackageName;
+
/** Returns the request token matching the user request that should be cancelled. */
@NonNull
public IBinder getToken() {
return mToken;
}
- public CancelUiRequest(@NonNull IBinder token) {
+ @NonNull
+ public String getAppPackageName() {
+ return mAppPackageName;
+ }
+
+ /**
+ * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
+ * will be silently cancelled.
+ */
+ public boolean shouldShowCancellationUi() {
+ return mShouldShowCancellationUi;
+ }
+
+ public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
+ @NonNull String appPackageName) {
mToken = token;
+ mShouldShowCancellationUi = shouldShowCancellationUi;
+ mAppPackageName = appPackageName;
}
private CancelUiRequest(@NonNull Parcel in) {
mToken = in.readStrongBinder();
AnnotationValidations.validate(NonNull.class, null, mToken);
+ mShouldShowCancellationUi = in.readBoolean();
+ mAppPackageName = in.readString8();
+ AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mToken);
+ dest.writeBoolean(mShouldShowCancellationUi);
+ dest.writeString8(mAppPackageName);
}
@Override
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index dcfef56..5e8372d 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -72,7 +72,8 @@
* @hide
*/
@NonNull
- public static Intent createCancelUiIntent(@NonNull IBinder requestToken) {
+ public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
+ boolean shouldShowCancellationUi, @NonNull String appPackageName) {
Intent intent = new Intent();
ComponentName componentName =
ComponentName.unflattenFromString(
@@ -81,7 +82,8 @@
com.android.internal.R.string
.config_credentialManagerDialogComponent));
intent.setComponent(componentName);
- intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, new CancelUiRequest(requestToken));
+ intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+ new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
return intent;
}
diff --git a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
index 140ef39..a5acf54 100644
--- a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
+++ b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
@@ -179,7 +179,7 @@
PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, TextUtils.formatSimple("Cannot resolve service %s",
- serviceComponent.getClass().getName()));
+ serviceComponent.getClassName()));
return null;
}
}
diff --git a/core/java/android/util/TeeWriter.java b/core/java/android/util/TeeWriter.java
new file mode 100644
index 0000000..439a0c2
--- /dev/null
+++ b/core/java/android/util/TeeWriter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Objects;
+
+/**
+ * Writer that offers to "tee" identical output to multiple underlying
+ * {@link Writer} instances.
+ *
+ * @see https://man7.org/linux/man-pages/man1/tee.1.html
+ * @hide
+ */
+public class TeeWriter extends Writer {
+ private final @NonNull Writer[] mWriters;
+
+ public TeeWriter(@NonNull Writer... writers) {
+ for (Writer writer : writers) {
+ Objects.requireNonNull(writer);
+ }
+ mWriters = writers;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ for (Writer writer : mWriters) {
+ writer.write(cbuf, off, len);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ for (Writer writer : mWriters) {
+ writer.flush();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (Writer writer : mWriters) {
+ writer.close();
+ }
+ }
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5476088..baefd85 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1218,47 +1218,51 @@
}
/**
- * Returns the display's HDR capabilities.
+ * Returns the current display mode's HDR capabilities.
*
* @see #isHdr()
*/
public HdrCapabilities getHdrCapabilities() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.userDisabledHdrTypes.length == 0) {
- return mDisplayInfo.hdrCapabilities;
- }
-
if (mDisplayInfo.hdrCapabilities == null) {
return null;
}
-
- ArraySet<Integer> enabledTypesSet = new ArraySet<>();
- for (int supportedType : mDisplayInfo.hdrCapabilities.getSupportedHdrTypes()) {
- boolean typeDisabled = false;
- for (int userDisabledType : mDisplayInfo.userDisabledHdrTypes) {
- if (supportedType == userDisabledType) {
- typeDisabled = true;
- break;
+ int[] supportedHdrTypes;
+ if (mDisplayInfo.userDisabledHdrTypes.length == 0) {
+ int[] modeSupportedHdrTypes = getMode().getSupportedHdrTypes();
+ supportedHdrTypes = Arrays.copyOf(modeSupportedHdrTypes,
+ modeSupportedHdrTypes.length);
+ } else {
+ ArraySet<Integer> enabledTypesSet = new ArraySet<>();
+ for (int supportedType : getMode().getSupportedHdrTypes()) {
+ if (!contains(mDisplayInfo.userDisabledHdrTypes, supportedType)) {
+ enabledTypesSet.add(supportedType);
}
}
- if (!typeDisabled) {
- enabledTypesSet.add(supportedType);
+
+ supportedHdrTypes = new int[enabledTypesSet.size()];
+ int index = 0;
+ for (int enabledType : enabledTypesSet) {
+ supportedHdrTypes[index++] = enabledType;
}
}
-
- int[] enabledTypes = new int[enabledTypesSet.size()];
- int index = 0;
- for (int enabledType : enabledTypesSet) {
- enabledTypes[index++] = enabledType;
- }
- return new HdrCapabilities(enabledTypes,
+ return new HdrCapabilities(supportedHdrTypes,
mDisplayInfo.hdrCapabilities.mMaxLuminance,
mDisplayInfo.hdrCapabilities.mMaxAverageLuminance,
mDisplayInfo.hdrCapabilities.mMinLuminance);
}
}
+ private boolean contains(int[] disabledHdrTypes, int hdrType) {
+ for (Integer disabledHdrFormat : disabledHdrTypes) {
+ if (disabledHdrFormat == hdrType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* @hide
* Returns the current mode's supported HDR types.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 24a0355..d35aff9 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -158,6 +158,14 @@
*/
public Matrix transform;
+ /**
+ * The input token for the window to which focus should be transferred when this input window
+ * can be successfully focused. If null, this input window will not transfer its focus to
+ * any other window.
+ */
+ @Nullable
+ public IBinder focusTransferTarget;
+
private native void nativeDispose();
public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
@@ -195,6 +203,7 @@
transform = new Matrix();
transform.set(other.transform);
}
+ focusTransferTarget = other.focusTransferTarget;
}
@Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0db52aa..bc6a3b5 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -265,7 +265,7 @@
int transformHint);
private static native void nativeRemoveCurrentInputFocus(long nativeObject, int displayId);
private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken,
- String windowName, IBinder focusedToken, String focusedWindowName, int displayId);
+ String windowName, int displayId);
private static native void nativeSetFrameTimelineVsync(long transactionObj,
long frameTimelineVsyncId);
private static native void nativeAddJankDataListener(long nativeListener,
@@ -3604,28 +3604,7 @@
*/
public Transaction setFocusedWindow(@NonNull IBinder token, String windowName,
int displayId) {
- nativeSetFocusedWindow(mNativeObject, token, windowName,
- null /* focusedToken */, null /* focusedWindowName */, displayId);
- return this;
- }
-
- /**
- * Set focus on the window identified by the input {@code token} if the window identified by
- * the input {@code focusedToken} is currently focused. If the {@code focusedToken} does not
- * have focus, the request is dropped.
- *
- * This is used by forward focus transfer requests from clients that host embedded windows,
- * and want to transfer focus to/from them.
- *
- * @hide
- */
- public Transaction requestFocusTransfer(@NonNull IBinder token,
- String windowName,
- @NonNull IBinder focusedToken,
- String focusedWindowName,
- int displayId) {
- nativeSetFocusedWindow(mNativeObject, token, windowName, focusedToken,
- focusedWindowName, displayId);
+ nativeSetFocusedWindow(mNativeObject, token, windowName, displayId);
return this;
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0560caf..9868144 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -57,18 +57,16 @@
SurfaceControl mLeash;
Rect mFrame;
Rect mAttachedFrame;
+ IBinder mFocusGrantToken;
- State(SurfaceControl sc, WindowManager.LayoutParams p,
- int displayId, IBinder inputChannelToken, IWindow client, SurfaceControl leash,
- Rect frame, Rect attachedFrame) {
+ State(SurfaceControl sc, WindowManager.LayoutParams p, int displayId, IWindow client,
+ SurfaceControl leash, Rect frame) {
mSurfaceControl = sc;
mParams.copyFrom(p);
mDisplayId = displayId;
- mInputChannelToken = inputChannelToken;
mClient = client;
mLeash = leash;
mFrame = frame;
- mAttachedFrame = attachedFrame;
}
};
@@ -182,45 +180,53 @@
.setParent(leash)
.build();
+ final State state = new State(sc, attrs, displayId, window, leash, /* frame= */ new Rect());
+ synchronized (this) {
+ State parentState = mStateForWindow.get(attrs.token);
+ if (parentState != null) {
+ state.mAttachedFrame = parentState.mFrame;
+ }
+
+ // Give the first window the mFocusGrantToken since that's the token the host can use
+ // to give focus to the embedded.
+ if (mStateForWindow.isEmpty()) {
+ state.mFocusGrantToken = mFocusGrantToken;
+ } else {
+ state.mFocusGrantToken = new Binder();
+ }
+
+ mStateForWindow.put(window.asBinder(), state);
+ }
+
+ if (state.mAttachedFrame == null) {
+ outAttachedFrame.set(0, 0, -1, -1);
+ } else {
+ outAttachedFrame.set(state.mAttachedFrame);
+ }
+ outSizeCompatScale[0] = 1f;
+
if (((attrs.inputFeatures &
WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)) {
try {
if (mRealWm instanceof IWindowSession.Stub) {
mRealWm.grantInputChannel(displayId,
new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
- window, mHostInputToken,
- attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type,
- attrs.token, mFocusGrantToken, attrs.getTitle().toString(),
+ window, mHostInputToken, attrs.flags, attrs.privateFlags,
+ attrs.inputFeatures, attrs.type,
+ attrs.token, state.mFocusGrantToken, attrs.getTitle().toString(),
outInputChannel);
} else {
mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token,
- mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
+ state.mFocusGrantToken, attrs.getTitle().toString(), outInputChannel);
}
+ state.mInputChannelToken =
+ outInputChannel != null ? outInputChannel.getToken() : null;
} catch (RemoteException e) {
Log.e(TAG, "Failed to grant input to surface: ", e);
}
}
- final State state = new State(sc, attrs, displayId,
- outInputChannel != null ? outInputChannel.getToken() : null, window,
- leash, /* frame= */ new Rect(), /* attachedFrame= */ null);
- Rect parentFrame = null;
- synchronized (this) {
- State parentState = mStateForWindow.get(attrs.token);
- if (parentState != null) {
- parentFrame = parentState.mFrame;
- }
- mStateForWindow.put(window.asBinder(), state);
- }
- state.mAttachedFrame = parentFrame;
- if (parentFrame == null) {
- outAttachedFrame.set(0, 0, -1, -1);
- } else {
- outAttachedFrame.set(parentFrame);
- }
- outSizeCompatScale[0] = 1f;
-
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 6872536..1840bcb 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1217,9 +1217,11 @@
* notify cursor/anchor locations.
*
* @param cursorUpdateMode any combination of update modes and filters:
- * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}, and date filters:
+ * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}, and data filters:
* {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}.
+ * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+ * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}.
* Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be
* rejected and method will return {@code false}.
* @return {@code true} if the request is scheduled. {@code false} to indicate that when the
@@ -1240,7 +1242,9 @@
* {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}
* @param cursorUpdateFilter any combination of data filters:
* {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}.
+ * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+ * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}.
*
* <p>Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be
* rejected and method will return {@code false}.</p>
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 515b95c..82cf073 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1648,6 +1648,7 @@
*
* @param userId user ID to query
* @return {@link List} of {@link InputMethodInfo}.
+ * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, int)
* @hide
*/
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@@ -1676,6 +1677,27 @@
}
/**
+ * Returns a list of enabled input method subtypes for the specified input method info for the
+ * specified user.
+ *
+ * @param imeId IME ID to be queried about.
+ * @param allowsImplicitlyEnabledSubtypes {@code true} to include implicitly enabled subtypes.
+ * @param userId user ID to be queried about.
+ * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
+ * different from the calling process user ID.
+ * @return {@link List} of {@link InputMethodSubtype}.
+ * @see #getEnabledInputMethodListAsUser(int)
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
+ @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+ return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(
+ Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes, userId);
+ }
+
+ /**
* @deprecated Use {@link InputMethodService#showStatusIcon(int)} instead. This method was
* intended for IME developers who should be accessing APIs through the service. APIs in this
* class are intended for app developers interacting with the IME.
diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java
index 4bd15f2..4cc7ec5 100644
--- a/core/java/android/window/RemoteTransition.java
+++ b/core/java/android/window/RemoteTransition.java
@@ -38,9 +38,18 @@
/** The application thread that will be running the remote transition. */
private @Nullable IApplicationThread mAppThread;
+ /** A name for this that can be used for debugging. */
+ private @Nullable String mDebugName;
+
/** Constructs with no app thread (animation runs in shell). */
public RemoteTransition(@NonNull IRemoteTransition remoteTransition) {
- this(remoteTransition, null /* appThread */);
+ this(remoteTransition, null /* appThread */, null /* debugName */);
+ }
+
+ /** Constructs with no app thread (animation runs in shell). */
+ public RemoteTransition(@NonNull IRemoteTransition remoteTransition,
+ @Nullable String debugName) {
+ this(remoteTransition, null /* appThread */, debugName);
}
/** Get the IBinder associated with the underlying IRemoteTransition. */
@@ -70,15 +79,19 @@
* The actual remote-transition interface used to run the transition animation.
* @param appThread
* The application thread that will be running the remote transition.
+ * @param debugName
+ * A name for this that can be used for debugging.
*/
@DataClass.Generated.Member
public RemoteTransition(
@NonNull IRemoteTransition remoteTransition,
- @Nullable IApplicationThread appThread) {
+ @Nullable IApplicationThread appThread,
+ @Nullable String debugName) {
this.mRemoteTransition = remoteTransition;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mRemoteTransition);
this.mAppThread = appThread;
+ this.mDebugName = debugName;
// onConstructed(); // You can define this method to get a callback
}
@@ -100,6 +113,14 @@
}
/**
+ * A name for this that can be used for debugging.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getDebugName() {
+ return mDebugName;
+ }
+
+ /**
* The actual remote-transition interface used to run the transition animation.
*/
@DataClass.Generated.Member
@@ -119,6 +140,15 @@
return this;
}
+ /**
+ * A name for this that can be used for debugging.
+ */
+ @DataClass.Generated.Member
+ public @NonNull RemoteTransition setDebugName(@NonNull String value) {
+ mDebugName = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -127,7 +157,8 @@
return "RemoteTransition { " +
"remoteTransition = " + mRemoteTransition + ", " +
- "appThread = " + mAppThread +
+ "appThread = " + mAppThread + ", " +
+ "debugName = " + mDebugName +
" }";
}
@@ -139,9 +170,11 @@
byte flg = 0;
if (mAppThread != null) flg |= 0x2;
+ if (mDebugName != null) flg |= 0x4;
dest.writeByte(flg);
dest.writeStrongInterface(mRemoteTransition);
if (mAppThread != null) dest.writeStrongInterface(mAppThread);
+ if (mDebugName != null) dest.writeString(mDebugName);
}
@Override
@@ -158,11 +191,13 @@
byte flg = in.readByte();
IRemoteTransition remoteTransition = IRemoteTransition.Stub.asInterface(in.readStrongBinder());
IApplicationThread appThread = (flg & 0x2) == 0 ? null : IApplicationThread.Stub.asInterface(in.readStrongBinder());
+ String debugName = (flg & 0x4) == 0 ? null : in.readString();
this.mRemoteTransition = remoteTransition;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mRemoteTransition);
this.mAppThread = appThread;
+ this.mDebugName = debugName;
// onConstructed(); // You can define this method to get a callback
}
@@ -182,10 +217,10 @@
};
@DataClass.Generated(
- time = 1630690027011L,
+ time = 1678926409863L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java",
- inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\nprivate @android.annotation.Nullable java.lang.String mDebugName\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 4c482460..0f3eef7 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -149,8 +149,11 @@
/** The task is launching behind home. */
public static final int FLAG_TASK_LAUNCHING_BEHIND = 1 << 19;
+ /** The task became the top-most task even if it didn't change visibility. */
+ public static final int FLAG_MOVED_TO_TOP = 1 << 20;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 20;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 21;
/** The change belongs to a window that won't contain activities. */
public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -179,6 +182,7 @@
FLAG_BACK_GESTURE_ANIMATED,
FLAG_NO_ANIMATION,
FLAG_TASK_LAUNCHING_BEHIND,
+ FLAG_MOVED_TO_TOP,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -190,6 +194,9 @@
private AnimationOptions mOptions;
+ /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */
+ private int mDebugId = -1;
+
/** @hide */
public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) {
mType = type;
@@ -202,6 +209,7 @@
in.readTypedList(mChanges, Change.CREATOR);
in.readTypedList(mRoots, Root.CREATOR);
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
+ mDebugId = in.readInt();
}
@Override
@@ -212,6 +220,7 @@
dest.writeTypedList(mChanges);
dest.writeTypedList(mRoots, flags);
dest.writeTypedObject(mOptions, flags);
+ dest.writeInt(mDebugId);
}
@NonNull
@@ -347,11 +356,24 @@
return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
}
+ /**
+ * Set an arbitrary "debug" id for this info. This id will not be used for any "real work",
+ * it is just for debugging and logging.
+ */
+ public void setDebugId(int id) {
+ mDebugId = id;
+ }
+
+ /** Get the "debug" id of this info. Do NOT use this for real work, only use for debugging. */
+ public int getDebugId() {
+ return mDebugId;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("{t=").append(transitTypeToString(mType)).append(" f=0x")
- .append(Integer.toHexString(mFlags)).append(" r=[");
+ sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
+ .append(" f=0x").append(Integer.toHexString(mFlags)).append(" r=[");
for (int i = 0; i < mRoots.size(); ++i) {
if (i > 0) {
sb.append(',');
@@ -510,6 +532,7 @@
*/
public TransitionInfo localRemoteCopy() {
final TransitionInfo out = new TransitionInfo(mType, mFlags);
+ out.mDebugId = mDebugId;
for (int i = 0; i < mChanges.size(); ++i) {
out.mChanges.add(mChanges.get(i).localRemoteCopy());
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index ad20432..9f804b1 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1586,61 +1586,109 @@
return mShortcutInfo;
}
+ /** Gets a string representation of a hierarchy-op type. */
+ public static String hopToString(int type) {
+ switch (type) {
+ case HIERARCHY_OP_TYPE_REPARENT: return "reparent";
+ case HIERARCHY_OP_TYPE_REORDER: return "reorder";
+ case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "ChildrenTasksReparent";
+ case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "SetLaunchRoot";
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot";
+ case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask";
+ case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot";
+ case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent";
+ case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut";
+ case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider";
+ case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
+ return "removeInsetsFrameProvider";
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
+ case HIERARCHY_OP_TYPE_REMOVE_TASK: return "RemoveTask";
+ case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
+ case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "ClearAdjacentRoot";
+ case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
+ return "setReparentLeafTaskIfRelaunch";
+ case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
+ return "addTaskFragmentOperation";
+ default: return "HOP(" + type + ")";
+ }
+ }
+
@Override
public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{").append(hopToString(mType)).append(": ");
switch (mType) {
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
- return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent
- + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly
- + " mWindowingMode=" + Arrays.toString(mWindowingModes)
- + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
+ sb.append("from=").append(mContainer).append(" to=").append(mReparent)
+ .append(" mToTop=").append(mToTop)
+ .append(" mReparentTopOnly=").append(mReparentTopOnly)
+ .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
+ .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
+ break;
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT:
- return "{SetLaunchRoot: container=" + mContainer
- + " mWindowingMode=" + Arrays.toString(mWindowingModes)
- + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
+ sb.append("container=").append(mContainer)
+ .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
+ .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
+ break;
case HIERARCHY_OP_TYPE_REPARENT:
- return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ")
- + mReparent + "}";
+ sb.append(mContainer).append(" to ").append(mToTop ? "top of " : "bottom of ")
+ .append(mReparent);
+ break;
case HIERARCHY_OP_TYPE_REORDER:
- return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
+ sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom");
+ break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
- return "{SetAdjacentRoot: container=" + mContainer
- + " adjacentRoot=" + mReparent + "}";
+ sb.append("container=").append(mContainer)
+ .append(" adjacentRoot=").append(mReparent);
+ break;
case HIERARCHY_OP_TYPE_LAUNCH_TASK:
- return "{LaunchTask: " + mLaunchOptions + "}";
+ sb.append(mLaunchOptions);
+ break;
case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
- return "{SetAdjacentFlagRoot: container=" + mContainer + " clearRoot=" + mToTop
- + "}";
+ sb.append("container=").append(mContainer).append(" clearRoot=").append(mToTop);
+ break;
case HIERARCHY_OP_TYPE_START_SHORTCUT:
- return "{StartShortcut: options=" + mLaunchOptions + " info=" + mShortcutInfo
- + "}";
+ sb.append("options=").append(mLaunchOptions)
+ .append(" info=").append(mShortcutInfo);
+ break;
+ case HIERARCHY_OP_TYPE_PENDING_INTENT:
+ sb.append("options=").append(mLaunchOptions);
+ break;
case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
- return "{addRectInsetsProvider: container=" + mContainer
- + " provider=" + mInsetsFrameProvider + "}";
case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
- return "{removeLocalInsetsProvider: container=" + mContainer
- + " provider=" + mInsetsFrameProvider + "}";
+ sb.append("container=").append(mContainer)
+ .append(" provider=").append(mInsetsFrameProvider);
+ break;
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
- return "{setAlwaysOnTop: container=" + mContainer
- + " alwaysOnTop=" + mAlwaysOnTop + "}";
+ sb.append("container=").append(mContainer)
+ .append(" alwaysOnTop=").append(mAlwaysOnTop);
+ break;
case HIERARCHY_OP_TYPE_REMOVE_TASK:
- return "{RemoveTask: task=" + mContainer + "}";
+ sb.append("task=").append(mContainer);
+ break;
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
- return "{finishActivity: activity=" + mContainer + "}";
+ sb.append("activity=").append(mContainer);
+ break;
case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
- return "{ClearAdjacentRoot: container=" + mContainer + "}";
+ sb.append("container=").append(mContainer);
+ break;
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
- return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
- + " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
+ sb.append("container= ").append(mContainer)
+ .append(" reparentLeafTaskIfRelaunch= ")
+ .append(mReparentLeafTaskIfRelaunch);
+ break;
case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
- return "{addTaskFragmentOperation: fragmentToken= " + mContainer
- + " operation= " + mTaskFragmentOperation + "}";
+ sb.append("fragmentToken= ").append(mContainer)
+ .append(" operation= ").append(mTaskFragmentOperation);
+ break;
default:
- return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
- + " mToTop=" + mToTop
- + " mWindowingMode=" + Arrays.toString(mWindowingModes)
- + " mActivityType=" + Arrays.toString(mActivityTypes) + "}";
+ sb.append("container=").append(mContainer)
+ .append(" reparent=").append(mReparent)
+ .append(" mToTop=").append(mToTop)
+ .append(" mWindowingMode=").append(Arrays.toString(mWindowingModes))
+ .append(" mActivityType=").append(Arrays.toString(mActivityTypes));
}
+ return sb.append("}").toString();
}
@Override
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0cb87fe..7ad2a68 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -524,11 +524,6 @@
public static final String DEFAULT_QR_CODE_SCANNER = "default_qr_code_scanner";
/**
- * (boolean) Whether the task manager entrypoint is enabled.
- */
- public static final String TASK_MANAGER_ENABLED = "task_manager_enabled";
-
- /**
* (boolean) Whether the task manager should show an attention grabbing dot when tasks changed.
*/
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6344568..4b9e77e 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -36,6 +36,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
@@ -244,6 +245,7 @@
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
public static final int CUJ_IME_INSETS_ANIMATION = 69;
public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
+ public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
private static final int NO_STATSD_LOGGING = -1;
@@ -323,6 +325,7 @@
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT,
};
private static class InstanceHolder {
@@ -418,6 +421,7 @@
CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
CUJ_IME_INSETS_ANIMATION,
CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
+ CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -968,6 +972,8 @@
return "IME_INSETS_ANIMATION";
case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
+ case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
+ return "LAUNCHER_OPEN_SEARCH_RESULT";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f7c03cd..ae58626 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -28,6 +28,7 @@
import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.view.KeyEvent;
import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.IAddTileResultCallback;
@@ -141,7 +142,7 @@
void addQsTile(in ComponentName tile);
void remQsTile(in ComponentName tile);
void clickQsTile(in ComponentName tile);
- void handleSystemKey(in int key);
+ void handleSystemKey(in KeyEvent key);
/**
* Methods to show toast messages for screen pinning
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index c1dbc87..3708859 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
+import android.view.KeyEvent;
import android.service.notification.StatusBarNotification;
import com.android.internal.logging.InstanceId;
@@ -110,7 +111,7 @@
void remTile(in ComponentName tile);
void clickTile(in ComponentName tile);
@UnsupportedAppUsage
- void handleSystemKey(in int key);
+ void handleSystemKey(in KeyEvent key);
int getLastSystemKey();
/**
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 241320f..416d991 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -74,6 +74,7 @@
WeakRefHandleField touchableRegionSurfaceControl;
jfieldID transform;
jfieldID windowToken;
+ jfieldID focusTransferTarget;
} gInputWindowHandleClassInfo;
static struct {
@@ -216,6 +217,17 @@
mInfo.windowToken.clear();
}
+ ScopedLocalRef<jobject>
+ focusTransferTargetObj(env,
+ env->GetObjectField(obj,
+ gInputWindowHandleClassInfo
+ .focusTransferTarget));
+ if (focusTransferTargetObj.get()) {
+ mInfo.focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
+ } else {
+ mInfo.focusTransferTarget.clear();
+ }
+
env->DeleteLocalRef(obj);
return true;
}
@@ -433,6 +445,9 @@
GET_FIELD_ID(gInputWindowHandleClassInfo.windowToken, clazz, "windowToken",
"Landroid/os/IBinder;");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.focusTransferTarget, clazz, "focusTransferTarget",
+ "Landroid/os/IBinder;");
+
jclass weakRefClazz;
FIND_CLASS(weakRefClazz, "java/lang/ref/Reference");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 03d6eec..e42c6f1 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1820,17 +1820,11 @@
}
static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj,
- jobject toTokenObj, jstring windowNameJstr,
- jobject focusedTokenObj, jstring focusedWindowNameJstr,
- jint displayId) {
+ jobject toTokenObj, jstring windowNameJstr, jint displayId) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
if (toTokenObj == NULL) return;
sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
- sp<IBinder> focusedToken;
- if (focusedTokenObj != NULL) {
- focusedToken = ibinderForJavaObject(env, focusedTokenObj);
- }
FocusRequest request;
request.token = toToken;
@@ -1839,11 +1833,6 @@
request.windowName = windowName.c_str();
}
- request.focusedToken = focusedToken;
- if (focusedWindowNameJstr != NULL) {
- ScopedUtfChars focusedWindowName(env, focusedWindowNameJstr);
- request.focusedWindowName = focusedWindowName.c_str();
- }
request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
request.displayId = displayId;
transaction->setFocusedWindow(request);
@@ -2236,7 +2225,7 @@
(void*)nativeGetHandle },
{"nativeSetFixedTransformHint", "(JJI)V",
(void*)nativeSetFixedTransformHint},
- {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Ljava/lang/String;Landroid/os/IBinder;Ljava/lang/String;I)V",
+ {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Ljava/lang/String;I)V",
(void*)nativeSetFocusedWindow},
{"nativeRemoveCurrentInputFocus", "(JI)V",
(void*)nativeRemoveCurrentInputFocus},
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index e62af74..bab4b6e 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -21,6 +21,7 @@
option java_outer_classname = "SettingsServiceProto";
import "frameworks/base/core/proto/android/providers/settings/config.proto";
+import "frameworks/base/core/proto/android/providers/settings/generation.proto";
import "frameworks/base/core/proto/android/providers/settings/global.proto";
import "frameworks/base/core/proto/android/providers/settings/secure.proto";
import "frameworks/base/core/proto/android/providers/settings/system.proto";
@@ -37,6 +38,9 @@
// Config settings
optional ConfigSettingsProto config_settings = 3;
+
+ // Generation registry stats
+ optional GenerationRegistryProto generation_registry = 4;
}
message UserSettingsProto {
diff --git a/core/proto/android/providers/settings/generation.proto b/core/proto/android/providers/settings/generation.proto
new file mode 100644
index 0000000..9dcbad2
--- /dev/null
+++ b/core/proto/android/providers/settings/generation.proto
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.providers.settings;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message GenerationRegistryProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+ optional int32 num_backing_stores = 1;
+ optional int32 num_max_backing_stores = 2;
+ repeated BackingStoreProto backing_stores = 3;
+}
+
+message BackingStoreProto {
+ optional int32 key = 1;
+ optional int32 backing_store_size = 2;
+ optional int32 num_cached_entries = 3;
+ repeated CacheEntryProto cache_entries = 4;
+}
+
+message CacheEntryProto {
+ optional string name = 1;
+ optional int32 generation = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index e6c8557..bb3089b 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -391,6 +391,9 @@
optional int32 last_drop_input_mode = 36;
optional int32 override_orientation = 37 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"];
optional bool should_send_compat_fake_focus = 38;
+ optional bool should_force_rotate_for_camera_compat = 39;
+ optional bool should_refresh_activity_for_camera_compat = 40;
+ optional bool should_refresh_activity_via_pause_for_camera_compat = 41;
}
/* represents WindowToken */
diff --git a/core/res/res/drawable-hdpi/pointer_alias.png b/core/res/res/drawable-hdpi/pointer_alias.png
new file mode 100644
index 0000000..d33fe3c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_all_scroll.png b/core/res/res/drawable-hdpi/pointer_all_scroll.png
new file mode 100644
index 0000000..095aadc
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_arrow.png b/core/res/res/drawable-hdpi/pointer_arrow.png
index 85d066e..a949a1a 100644
--- a/core/res/res/drawable-hdpi/pointer_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_cell.png b/core/res/res/drawable-hdpi/pointer_cell.png
new file mode 100644
index 0000000..76910e6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_context_menu.png b/core/res/res/drawable-hdpi/pointer_context_menu.png
new file mode 100644
index 0000000..c45d29b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_copy.png b/core/res/res/drawable-hdpi/pointer_copy.png
new file mode 100644
index 0000000..c5eda2e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_crosshair.png b/core/res/res/drawable-hdpi/pointer_crosshair.png
new file mode 100644
index 0000000..be767b2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_grab.png b/core/res/res/drawable-hdpi/pointer_grab.png
new file mode 100644
index 0000000..26da04d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_grabbing.png b/core/res/res/drawable-hdpi/pointer_grabbing.png
new file mode 100644
index 0000000..f4031a9
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_hand.png b/core/res/res/drawable-hdpi/pointer_hand.png
new file mode 100644
index 0000000..a7ae55f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_help.png b/core/res/res/drawable-hdpi/pointer_help.png
new file mode 100644
index 0000000..a3afdb6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
new file mode 100644
index 0000000..9388f16
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_nodrop.png b/core/res/res/drawable-hdpi/pointer_nodrop.png
new file mode 100644
index 0000000..7043323
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_spot_anchor.png b/core/res/res/drawable-hdpi/pointer_spot_anchor.png
index 784f613..4b74e2d 100644
--- a/core/res/res/drawable-hdpi/pointer_spot_anchor.png
+++ b/core/res/res/drawable-hdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_spot_hover.png b/core/res/res/drawable-hdpi/pointer_spot_hover.png
index 0e8353c..68d6e4a 100644
--- a/core/res/res/drawable-hdpi/pointer_spot_hover.png
+++ b/core/res/res/drawable-hdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_spot_touch.png b/core/res/res/drawable-hdpi/pointer_spot_touch.png
index 3ad9b10..fda831f 100644
--- a/core/res/res/drawable-hdpi/pointer_spot_touch.png
+++ b/core/res/res/drawable-hdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_text.png b/core/res/res/drawable-hdpi/pointer_text.png
new file mode 100644
index 0000000..ab0c80a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
new file mode 100644
index 0000000..ab52bff
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
new file mode 100644
index 0000000..1250d35
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
new file mode 100644
index 0000000..6730c7b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_text.png b/core/res/res/drawable-hdpi/pointer_vertical_text.png
new file mode 100644
index 0000000..f079bc1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_zoom_in.png b/core/res/res/drawable-hdpi/pointer_zoom_in.png
new file mode 100644
index 0000000..a3dc84b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_zoom_out.png b/core/res/res/drawable-hdpi/pointer_zoom_out.png
new file mode 100644
index 0000000..3ee31bd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_alias.png b/core/res/res/drawable-mdpi/pointer_alias.png
index 8f61a39..619567a 100644
--- a/core/res/res/drawable-mdpi/pointer_alias.png
+++ b/core/res/res/drawable-mdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_alias_large.png b/core/res/res/drawable-mdpi/pointer_alias_large.png
index 606774d..b3f382e 100644
--- a/core/res/res/drawable-mdpi/pointer_alias_large.png
+++ b/core/res/res/drawable-mdpi/pointer_alias_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png
index a897ef4..3db456e 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
index c29db87..120e1d7 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_arrow.png b/core/res/res/drawable-mdpi/pointer_arrow.png
index 7a74ec1..77e4354 100644
--- a/core/res/res/drawable-mdpi/pointer_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_arrow_large.png b/core/res/res/drawable-mdpi/pointer_arrow_large.png
index 9f59c4c..e28a7a5 100644
--- a/core/res/res/drawable-mdpi/pointer_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_cell.png b/core/res/res/drawable-mdpi/pointer_cell.png
index b521389..e5ce946 100644
--- a/core/res/res/drawable-mdpi/pointer_cell.png
+++ b/core/res/res/drawable-mdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_cell_large.png b/core/res/res/drawable-mdpi/pointer_cell_large.png
index 3dec5e5..fcb9fc8 100644
--- a/core/res/res/drawable-mdpi/pointer_cell_large.png
+++ b/core/res/res/drawable-mdpi/pointer_cell_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png
index 4e1ba4e..e0e849d 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu_large.png b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
index 7c9e250..e8c9be4 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png
index 254485c..e731108 100644
--- a/core/res/res/drawable-mdpi/pointer_copy.png
+++ b/core/res/res/drawable-mdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_copy_large.png b/core/res/res/drawable-mdpi/pointer_copy_large.png
index 2f0e082..15ccb04 100644
--- a/core/res/res/drawable-mdpi/pointer_copy_large.png
+++ b/core/res/res/drawable-mdpi/pointer_copy_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_crosshair.png b/core/res/res/drawable-mdpi/pointer_crosshair.png
index 8a06c77..be6bd34 100644
--- a/core/res/res/drawable-mdpi/pointer_crosshair.png
+++ b/core/res/res/drawable-mdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_crosshair_large.png b/core/res/res/drawable-mdpi/pointer_crosshair_large.png
index 51faf96..657df8d 100644
--- a/core/res/res/drawable-mdpi/pointer_crosshair_large.png
+++ b/core/res/res/drawable-mdpi/pointer_crosshair_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png
index 0e0ea2e..d625b55 100644
--- a/core/res/res/drawable-mdpi/pointer_grab.png
+++ b/core/res/res/drawable-mdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grab_large.png b/core/res/res/drawable-mdpi/pointer_grab_large.png
index 44a171c..9d36df0 100644
--- a/core/res/res/drawable-mdpi/pointer_grab_large.png
+++ b/core/res/res/drawable-mdpi/pointer_grab_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png
index 9deb64c..71bb17b 100644
--- a/core/res/res/drawable-mdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-mdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_grabbing_large.png b/core/res/res/drawable-mdpi/pointer_grabbing_large.png
index b602d2f..5574b07 100644
--- a/core/res/res/drawable-mdpi/pointer_grabbing_large.png
+++ b/core/res/res/drawable-mdpi/pointer_grabbing_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png
index c614d9e..d7f7bed 100644
--- a/core/res/res/drawable-mdpi/pointer_hand.png
+++ b/core/res/res/drawable-mdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_hand_large.png b/core/res/res/drawable-mdpi/pointer_hand_large.png
index 464ec28..f775464 100644
--- a/core/res/res/drawable-mdpi/pointer_hand_large.png
+++ b/core/res/res/drawable-mdpi/pointer_hand_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png
index d54b2b1..286242c 100644
--- a/core/res/res/drawable-mdpi/pointer_help.png
+++ b/core/res/res/drawable-mdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help_large.png b/core/res/res/drawable-mdpi/pointer_help_large.png
index 69d1e41..27f4a84 100644
--- a/core/res/res/drawable-mdpi/pointer_help_large.png
+++ b/core/res/res/drawable-mdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
index a2951a9..20f319a 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
index 7086106..33ef5c9 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png
index aa92895..931b740 100644
--- a/core/res/res/drawable-mdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-mdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_nodrop_large.png b/core/res/res/drawable-mdpi/pointer_nodrop_large.png
index e150d04..88f77d3 100644
--- a/core/res/res/drawable-mdpi/pointer_nodrop_large.png
+++ b/core/res/res/drawable-mdpi/pointer_nodrop_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
index 48d638b..a8bc03b 100644
--- a/core/res/res/drawable-mdpi/pointer_spot_anchor.png
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png
index b304815..c3672b2 100644
--- a/core/res/res/drawable-mdpi/pointer_spot_hover.png
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png
index 659f809..1f146d2 100644
--- a/core/res/res/drawable-mdpi/pointer_spot_touch.png
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_text.png b/core/res/res/drawable-mdpi/pointer_text.png
index 34d1698..68a5535 100644
--- a/core/res/res/drawable-mdpi/pointer_text.png
+++ b/core/res/res/drawable-mdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_text_large.png b/core/res/res/drawable-mdpi/pointer_text_large.png
index 2fba190..599dc69 100644
--- a/core/res/res/drawable-mdpi/pointer_text_large.png
+++ b/core/res/res/drawable-mdpi/pointer_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
index b0cd92c..fe7d496 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
index eecaa89..7b2e20c 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
index f8d3527..95a6620 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
index 9d47ecf..2e2904b 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
index 48c9379..ae6bfed 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
index fd777b1..3beb1d1 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text.png b/core/res/res/drawable-mdpi/pointer_vertical_text.png
index 9fcbcba..06a536b 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_text.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text_large.png b/core/res/res/drawable-mdpi/pointer_vertical_text_large.png
index 1cbe49a..f03179b 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_text_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_0.png b/core/res/res/drawable-mdpi/pointer_wait_0.png
index ae32a44..aefdb46 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_0.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_1.png b/core/res/res/drawable-mdpi/pointer_wait_1.png
index afadc31..1939660 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_1.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_10.png b/core/res/res/drawable-mdpi/pointer_wait_10.png
index 4e5f3b0..cd3a8f5 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_10.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_10.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_11.png b/core/res/res/drawable-mdpi/pointer_wait_11.png
index f895e53..e8894ed 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_11.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_11.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_12.png b/core/res/res/drawable-mdpi/pointer_wait_12.png
index 7a155f5..f5af8b0 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_12.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_12.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_13.png b/core/res/res/drawable-mdpi/pointer_wait_13.png
index a9ae639..06e4462 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_13.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_13.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_14.png b/core/res/res/drawable-mdpi/pointer_wait_14.png
index 6761dda..7f0ac45 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_14.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_14.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_15.png b/core/res/res/drawable-mdpi/pointer_wait_15.png
index 98821ed..577fb78 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_15.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_15.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_16.png b/core/res/res/drawable-mdpi/pointer_wait_16.png
index 72f3853..d111d0b 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_16.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_16.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_17.png b/core/res/res/drawable-mdpi/pointer_wait_17.png
index a7452ed..55bd35a 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_17.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_17.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_18.png b/core/res/res/drawable-mdpi/pointer_wait_18.png
index ecb4f72..a1870ca 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_18.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_18.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_19.png b/core/res/res/drawable-mdpi/pointer_wait_19.png
index 1ce5d70..ffc4435 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_19.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_19.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_2.png b/core/res/res/drawable-mdpi/pointer_wait_2.png
index d42278a..04314b7 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_2.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_20.png b/core/res/res/drawable-mdpi/pointer_wait_20.png
index 2736fea..c98abab 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_20.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_20.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_21.png b/core/res/res/drawable-mdpi/pointer_wait_21.png
index e2fafd1..778e829 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_21.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_21.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_22.png b/core/res/res/drawable-mdpi/pointer_wait_22.png
index 24bd01a..9d61756 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_22.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_22.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_23.png b/core/res/res/drawable-mdpi/pointer_wait_23.png
index 26c6129..68c1def 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_23.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_23.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_24.png b/core/res/res/drawable-mdpi/pointer_wait_24.png
index 2597963..cb4e59f 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_24.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_24.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_25.png b/core/res/res/drawable-mdpi/pointer_wait_25.png
index c925d82..64662b2 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_25.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_25.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_26.png b/core/res/res/drawable-mdpi/pointer_wait_26.png
index 7c3735d..8a4a730 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_26.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_26.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_27.png b/core/res/res/drawable-mdpi/pointer_wait_27.png
index d4f2f65..6578225 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_27.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_27.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_28.png b/core/res/res/drawable-mdpi/pointer_wait_28.png
index 582c276..233efc3 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_28.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_28.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_29.png b/core/res/res/drawable-mdpi/pointer_wait_29.png
index f79f715..2513064 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_29.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_29.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_3.png b/core/res/res/drawable-mdpi/pointer_wait_3.png
index efc766e..a4b3de5 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_3.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_3.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_30.png b/core/res/res/drawable-mdpi/pointer_wait_30.png
index 636d793..f3dcdbb 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_30.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_30.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_31.png b/core/res/res/drawable-mdpi/pointer_wait_31.png
index 8f41a53..c0709aa 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_31.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_31.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_32.png b/core/res/res/drawable-mdpi/pointer_wait_32.png
index deef9b7..2456313 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_32.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_32.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_33.png b/core/res/res/drawable-mdpi/pointer_wait_33.png
index 6cad76b..d5506a8 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_33.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_33.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_34.png b/core/res/res/drawable-mdpi/pointer_wait_34.png
index 4b25825..365213d 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_34.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_34.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_35.png b/core/res/res/drawable-mdpi/pointer_wait_35.png
index ccfaf74..577c7b6 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_35.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_35.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_36.png b/core/res/res/drawable-mdpi/pointer_wait_36.png
new file mode 100644
index 0000000..9f7202b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_36.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_37.png b/core/res/res/drawable-mdpi/pointer_wait_37.png
new file mode 100644
index 0000000..7a041af
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_37.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_38.png b/core/res/res/drawable-mdpi/pointer_wait_38.png
new file mode 100644
index 0000000..d615452
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_38.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_39.png b/core/res/res/drawable-mdpi/pointer_wait_39.png
new file mode 100644
index 0000000..b6ecf0f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_39.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_4.png b/core/res/res/drawable-mdpi/pointer_wait_4.png
index d39d13a..f2a7759 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_4.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_4.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_40.png b/core/res/res/drawable-mdpi/pointer_wait_40.png
new file mode 100644
index 0000000..408ac15
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_40.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_41.png b/core/res/res/drawable-mdpi/pointer_wait_41.png
new file mode 100644
index 0000000..54f38cf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_41.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_42.png b/core/res/res/drawable-mdpi/pointer_wait_42.png
new file mode 100644
index 0000000..7894cf5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_42.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_43.png b/core/res/res/drawable-mdpi/pointer_wait_43.png
new file mode 100644
index 0000000..8bd34af
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_43.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_44.png b/core/res/res/drawable-mdpi/pointer_wait_44.png
new file mode 100644
index 0000000..b79e17b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_44.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_45.png b/core/res/res/drawable-mdpi/pointer_wait_45.png
new file mode 100644
index 0000000..7105121
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_45.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_46.png b/core/res/res/drawable-mdpi/pointer_wait_46.png
new file mode 100644
index 0000000..09b2f61
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_46.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_47.png b/core/res/res/drawable-mdpi/pointer_wait_47.png
new file mode 100644
index 0000000..2415548a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_47.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_48.png b/core/res/res/drawable-mdpi/pointer_wait_48.png
new file mode 100644
index 0000000..7b51615
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_48.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_49.png b/core/res/res/drawable-mdpi/pointer_wait_49.png
new file mode 100644
index 0000000..985c7bc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_49.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_5.png b/core/res/res/drawable-mdpi/pointer_wait_5.png
index 1c5a7de..a6f9941 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_5.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_5.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_50.png b/core/res/res/drawable-mdpi/pointer_wait_50.png
new file mode 100644
index 0000000..c8b031f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_50.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_51.png b/core/res/res/drawable-mdpi/pointer_wait_51.png
new file mode 100644
index 0000000..74e74c9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_51.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_52.png b/core/res/res/drawable-mdpi/pointer_wait_52.png
new file mode 100644
index 0000000..e42252d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_52.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_53.png b/core/res/res/drawable-mdpi/pointer_wait_53.png
new file mode 100644
index 0000000..860de2a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_53.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_54.png b/core/res/res/drawable-mdpi/pointer_wait_54.png
new file mode 100644
index 0000000..eb47cc1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_54.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_55.png b/core/res/res/drawable-mdpi/pointer_wait_55.png
new file mode 100644
index 0000000..ce853acc
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_55.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_56.png b/core/res/res/drawable-mdpi/pointer_wait_56.png
new file mode 100644
index 0000000..e17ec11
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_56.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_57.png b/core/res/res/drawable-mdpi/pointer_wait_57.png
new file mode 100644
index 0000000..f450f04
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_57.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_58.png b/core/res/res/drawable-mdpi/pointer_wait_58.png
new file mode 100644
index 0000000..f3d26dd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_58.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_59.png b/core/res/res/drawable-mdpi/pointer_wait_59.png
new file mode 100644
index 0000000..5220f3c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_59.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_6.png b/core/res/res/drawable-mdpi/pointer_wait_6.png
index 5113b27..5f2b1f9 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_6.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_6.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_60.png b/core/res/res/drawable-mdpi/pointer_wait_60.png
new file mode 100644
index 0000000..b01a0cd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_60.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_61.png b/core/res/res/drawable-mdpi/pointer_wait_61.png
new file mode 100644
index 0000000..07a132d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_61.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_62.png b/core/res/res/drawable-mdpi/pointer_wait_62.png
new file mode 100644
index 0000000..52e9768
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_62.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_63.png b/core/res/res/drawable-mdpi/pointer_wait_63.png
new file mode 100644
index 0000000..85b36f7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_63.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_64.png b/core/res/res/drawable-mdpi/pointer_wait_64.png
new file mode 100644
index 0000000..d684752
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_64.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_65.png b/core/res/res/drawable-mdpi/pointer_wait_65.png
new file mode 100644
index 0000000..7c0ee30
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_65.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_66.png b/core/res/res/drawable-mdpi/pointer_wait_66.png
new file mode 100644
index 0000000..54a7204
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_66.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_67.png b/core/res/res/drawable-mdpi/pointer_wait_67.png
new file mode 100644
index 0000000..8416304
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_67.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_68.png b/core/res/res/drawable-mdpi/pointer_wait_68.png
new file mode 100644
index 0000000..af2e5e2
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_68.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_69.png b/core/res/res/drawable-mdpi/pointer_wait_69.png
new file mode 100644
index 0000000..dd440d7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_69.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_7.png b/core/res/res/drawable-mdpi/pointer_wait_7.png
index 766a716..d950c53 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_7.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_7.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_70.png b/core/res/res/drawable-mdpi/pointer_wait_70.png
new file mode 100644
index 0000000..3b3843b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_70.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_71.png b/core/res/res/drawable-mdpi/pointer_wait_71.png
new file mode 100644
index 0000000..e8aba4c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_71.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_72.png b/core/res/res/drawable-mdpi/pointer_wait_72.png
new file mode 100644
index 0000000..17c30d50
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_72.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_73.png b/core/res/res/drawable-mdpi/pointer_wait_73.png
new file mode 100644
index 0000000..9336a80
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_73.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_74.png b/core/res/res/drawable-mdpi/pointer_wait_74.png
new file mode 100644
index 0000000..1f18ea5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_74.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_75.png b/core/res/res/drawable-mdpi/pointer_wait_75.png
new file mode 100644
index 0000000..24d2b774
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_75.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_76.png b/core/res/res/drawable-mdpi/pointer_wait_76.png
new file mode 100644
index 0000000..21cd4b1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_76.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_77.png b/core/res/res/drawable-mdpi/pointer_wait_77.png
new file mode 100644
index 0000000..53d6532
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_77.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_78.png b/core/res/res/drawable-mdpi/pointer_wait_78.png
new file mode 100644
index 0000000..769f1c8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_78.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_79.png b/core/res/res/drawable-mdpi/pointer_wait_79.png
new file mode 100644
index 0000000..3f89a84
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_79.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_8.png b/core/res/res/drawable-mdpi/pointer_wait_8.png
index 80fb2d9..c3026ea 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_8.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_8.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_80.png b/core/res/res/drawable-mdpi/pointer_wait_80.png
new file mode 100644
index 0000000..4cf0e15
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_wait_80.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_wait_9.png b/core/res/res/drawable-mdpi/pointer_wait_9.png
index db07e87..f1fb134 100644
--- a/core/res/res/drawable-mdpi/pointer_wait_9.png
+++ b/core/res/res/drawable-mdpi/pointer_wait_9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in.png b/core/res/res/drawable-mdpi/pointer_zoom_in.png
index 17c4e66..ef4e4a5 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_in.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in_large.png b/core/res/res/drawable-mdpi/pointer_zoom_in_large.png
index 9b0fa7f..c484ce8 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_in_large.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_in_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out.png b/core/res/res/drawable-mdpi/pointer_zoom_out.png
index 742f705..af725a4 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_out.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out_large.png b/core/res/res/drawable-mdpi/pointer_zoom_out_large.png
index 1a9ec86..448ea45 100644
--- a/core/res/res/drawable-mdpi/pointer_zoom_out_large.png
+++ b/core/res/res/drawable-mdpi/pointer_zoom_out_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_alias.png b/core/res/res/drawable-xhdpi/pointer_alias.png
index fe0fd25..d71ba71 100644
--- a/core/res/res/drawable-xhdpi/pointer_alias.png
+++ b/core/res/res/drawable-xhdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_alias_large.png b/core/res/res/drawable-xhdpi/pointer_alias_large.png
index ad595ed..b178901 100644
--- a/core/res/res/drawable-xhdpi/pointer_alias_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_alias_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
index e2374ec..e9d05d5 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
index 6029f52..1fd54fb 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_arrow.png b/core/res/res/drawable-xhdpi/pointer_arrow.png
index 1cfd033..b2608c5 100644
--- a/core/res/res/drawable-xhdpi/pointer_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_arrow_large.png
index dc9007a..6019c5a 100644
--- a/core/res/res/drawable-xhdpi/pointer_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_cell.png b/core/res/res/drawable-xhdpi/pointer_cell.png
index 4ca09e3..e96b8bd 100644
--- a/core/res/res/drawable-xhdpi/pointer_cell.png
+++ b/core/res/res/drawable-xhdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_cell_large.png b/core/res/res/drawable-xhdpi/pointer_cell_large.png
index 50119b7..01d0270 100644
--- a/core/res/res/drawable-xhdpi/pointer_cell_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_cell_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png
index 05a59f8..d4b2bde 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
index 148cf87..977df10 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png
index 0cdbf21..5b6cc5b 100644
--- a/core/res/res/drawable-xhdpi/pointer_copy.png
+++ b/core/res/res/drawable-xhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_copy_large.png b/core/res/res/drawable-xhdpi/pointer_copy_large.png
index 0e1350c..d78a410 100644
--- a/core/res/res/drawable-xhdpi/pointer_copy_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_copy_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair.png b/core/res/res/drawable-xhdpi/pointer_crosshair.png
index 86c649c..d384c94 100644
--- a/core/res/res/drawable-xhdpi/pointer_crosshair.png
+++ b/core/res/res/drawable-xhdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair_large.png b/core/res/res/drawable-xhdpi/pointer_crosshair_large.png
index fc59291..165daf3 100644
--- a/core/res/res/drawable-xhdpi/pointer_crosshair_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_crosshair_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png
index b5c28ba..46dd3ee 100644
--- a/core/res/res/drawable-xhdpi/pointer_grab.png
+++ b/core/res/res/drawable-xhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grab_large.png b/core/res/res/drawable-xhdpi/pointer_grab_large.png
index df98d89..1c7e63e 100644
--- a/core/res/res/drawable-xhdpi/pointer_grab_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_grab_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png
index 6aba589..2fb8a9c 100644
--- a/core/res/res/drawable-xhdpi/pointer_grabbing.png
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
index f2d043c..3467a03 100644
--- a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png
index 486cb24..926310c 100644
--- a/core/res/res/drawable-xhdpi/pointer_hand.png
+++ b/core/res/res/drawable-xhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_hand_large.png b/core/res/res/drawable-xhdpi/pointer_hand_large.png
index 3113589..546b222 100644
--- a/core/res/res/drawable-xhdpi/pointer_hand_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_hand_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png
index abcf923..5a6805c 100644
--- a/core/res/res/drawable-xhdpi/pointer_help.png
+++ b/core/res/res/drawable-xhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help_large.png b/core/res/res/drawable-xhdpi/pointer_help_large.png
index b745e1e..4bdc3d1 100644
--- a/core/res/res/drawable-xhdpi/pointer_help_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
index 299ae11..caf2a97 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
index 9c8fa5c..2f22640 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png
index 8e93f33..fdfc267 100644
--- a/core/res/res/drawable-xhdpi/pointer_nodrop.png
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
index a392da7..2b5e8a4 100644
--- a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_spot_anchor.png b/core/res/res/drawable-xhdpi/pointer_spot_anchor.png
index a1dcc14..c740755 100644
--- a/core/res/res/drawable-xhdpi/pointer_spot_anchor.png
+++ b/core/res/res/drawable-xhdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_spot_hover.png b/core/res/res/drawable-xhdpi/pointer_spot_hover.png
index 668f841..e734427 100644
--- a/core/res/res/drawable-xhdpi/pointer_spot_hover.png
+++ b/core/res/res/drawable-xhdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_spot_touch.png b/core/res/res/drawable-xhdpi/pointer_spot_touch.png
index 2e922db..4394050 100644
--- a/core/res/res/drawable-xhdpi/pointer_spot_touch.png
+++ b/core/res/res/drawable-xhdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_text.png b/core/res/res/drawable-xhdpi/pointer_text.png
index 0cebeae..2c0184d 100644
--- a/core/res/res/drawable-xhdpi/pointer_text.png
+++ b/core/res/res/drawable-xhdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_text_large.png b/core/res/res/drawable-xhdpi/pointer_text_large.png
index d9ce209..97db3ec 100644
--- a/core/res/res/drawable-xhdpi/pointer_text_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
index 5454a8b..a36deb3 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
index fcfa405..6870e23 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
index a4268e4..c8d6d1f 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
index 39c5f1a..5bfb771 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
index 95ca954..720df91 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
index 191f103..82b30d1 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text.png b/core/res/res/drawable-xhdpi/pointer_vertical_text.png
index a07d091..4ec8eea 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_text.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png
index d3f729a..fab7412 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_0.png b/core/res/res/drawable-xhdpi/pointer_wait_0.png
index 013aaf6..4c558b2 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_0.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_0.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_1.png b/core/res/res/drawable-xhdpi/pointer_wait_1.png
index 7fb0300..e86769d 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_1.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_1.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_10.png b/core/res/res/drawable-xhdpi/pointer_wait_10.png
index 90efa0a..b0f9d87 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_10.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_10.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_11.png b/core/res/res/drawable-xhdpi/pointer_wait_11.png
index 7c303a2..4b853ab 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_11.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_11.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_12.png b/core/res/res/drawable-xhdpi/pointer_wait_12.png
index 20a1fa8..59bf8a8 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_12.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_12.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_13.png b/core/res/res/drawable-xhdpi/pointer_wait_13.png
index 4585a39..5c618d7 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_13.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_13.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_14.png b/core/res/res/drawable-xhdpi/pointer_wait_14.png
index bdf0e5c..c4fb4ac 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_14.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_14.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_15.png b/core/res/res/drawable-xhdpi/pointer_wait_15.png
index e39c28c..24bdc15 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_15.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_15.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_16.png b/core/res/res/drawable-xhdpi/pointer_wait_16.png
index 7288141..a55f280 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_16.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_16.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_17.png b/core/res/res/drawable-xhdpi/pointer_wait_17.png
index b35da18..fc61645 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_17.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_17.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_18.png b/core/res/res/drawable-xhdpi/pointer_wait_18.png
index 0a61dd6..6f72345 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_18.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_18.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_19.png b/core/res/res/drawable-xhdpi/pointer_wait_19.png
index 1920c77..05d2dbb 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_19.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_19.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_2.png b/core/res/res/drawable-xhdpi/pointer_wait_2.png
index c6b3861..5b018a3 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_2.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_2.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_20.png b/core/res/res/drawable-xhdpi/pointer_wait_20.png
index b27358b..af141d8 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_20.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_20.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_21.png b/core/res/res/drawable-xhdpi/pointer_wait_21.png
index c1f9076..90cd89c 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_21.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_21.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_22.png b/core/res/res/drawable-xhdpi/pointer_wait_22.png
index 6cc0c13..41165cb 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_22.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_22.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_23.png b/core/res/res/drawable-xhdpi/pointer_wait_23.png
index 7df525c..46305db 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_23.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_23.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_24.png b/core/res/res/drawable-xhdpi/pointer_wait_24.png
index 008e273..53ecd9b 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_24.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_24.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_25.png b/core/res/res/drawable-xhdpi/pointer_wait_25.png
index 59f02d9..e413997 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_25.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_25.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_26.png b/core/res/res/drawable-xhdpi/pointer_wait_26.png
index 852b0bf..081ef84 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_26.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_26.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_27.png b/core/res/res/drawable-xhdpi/pointer_wait_27.png
index aba6292..19f29ac 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_27.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_27.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_28.png b/core/res/res/drawable-xhdpi/pointer_wait_28.png
index e59af43..9f9f906 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_28.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_28.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_29.png b/core/res/res/drawable-xhdpi/pointer_wait_29.png
index a195c29..07ff05e 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_29.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_29.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_3.png b/core/res/res/drawable-xhdpi/pointer_wait_3.png
index fb40e6f..e77332e 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_3.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_3.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_30.png b/core/res/res/drawable-xhdpi/pointer_wait_30.png
index 0d19d28..7d31416 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_30.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_30.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_31.png b/core/res/res/drawable-xhdpi/pointer_wait_31.png
index 49066b6..651a33d 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_31.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_31.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_32.png b/core/res/res/drawable-xhdpi/pointer_wait_32.png
index 43f7543..b09b961 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_32.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_32.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_33.png b/core/res/res/drawable-xhdpi/pointer_wait_33.png
index 3664ea8..5b260c4a 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_33.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_33.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_34.png b/core/res/res/drawable-xhdpi/pointer_wait_34.png
index e4c3919..fdcfc64 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_34.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_34.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_35.png b/core/res/res/drawable-xhdpi/pointer_wait_35.png
index e58777a..e3da4b8 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_35.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_35.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_36.png b/core/res/res/drawable-xhdpi/pointer_wait_36.png
new file mode 100644
index 0000000..1a1d7b7
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_36.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_37.png b/core/res/res/drawable-xhdpi/pointer_wait_37.png
new file mode 100644
index 0000000..c53c43c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_37.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_38.png b/core/res/res/drawable-xhdpi/pointer_wait_38.png
new file mode 100644
index 0000000..16cf47f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_38.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_39.png b/core/res/res/drawable-xhdpi/pointer_wait_39.png
new file mode 100644
index 0000000..2adb861
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_39.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_4.png b/core/res/res/drawable-xhdpi/pointer_wait_4.png
index 552b735..18bd826 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_4.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_4.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_40.png b/core/res/res/drawable-xhdpi/pointer_wait_40.png
new file mode 100644
index 0000000..da7861f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_40.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_41.png b/core/res/res/drawable-xhdpi/pointer_wait_41.png
new file mode 100644
index 0000000..d0ba242
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_41.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_42.png b/core/res/res/drawable-xhdpi/pointer_wait_42.png
new file mode 100644
index 0000000..a90a138
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_42.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_43.png b/core/res/res/drawable-xhdpi/pointer_wait_43.png
new file mode 100644
index 0000000..4256b25
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_43.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_44.png b/core/res/res/drawable-xhdpi/pointer_wait_44.png
new file mode 100644
index 0000000..9e5fe21
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_44.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_45.png b/core/res/res/drawable-xhdpi/pointer_wait_45.png
new file mode 100644
index 0000000..19dd43f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_45.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_46.png b/core/res/res/drawable-xhdpi/pointer_wait_46.png
new file mode 100644
index 0000000..c91001f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_46.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_47.png b/core/res/res/drawable-xhdpi/pointer_wait_47.png
new file mode 100644
index 0000000..d481e42
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_47.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_48.png b/core/res/res/drawable-xhdpi/pointer_wait_48.png
new file mode 100644
index 0000000..299585b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_48.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_49.png b/core/res/res/drawable-xhdpi/pointer_wait_49.png
new file mode 100644
index 0000000..cd03213
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_49.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_5.png b/core/res/res/drawable-xhdpi/pointer_wait_5.png
index cd2bfa1..3abd5db 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_5.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_5.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_50.png b/core/res/res/drawable-xhdpi/pointer_wait_50.png
new file mode 100644
index 0000000..72122a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_50.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_51.png b/core/res/res/drawable-xhdpi/pointer_wait_51.png
new file mode 100644
index 0000000..94398fd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_51.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_52.png b/core/res/res/drawable-xhdpi/pointer_wait_52.png
new file mode 100644
index 0000000..a4561bd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_52.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_53.png b/core/res/res/drawable-xhdpi/pointer_wait_53.png
new file mode 100644
index 0000000..a8f417a
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_53.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_54.png b/core/res/res/drawable-xhdpi/pointer_wait_54.png
new file mode 100644
index 0000000..cedab09
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_54.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_55.png b/core/res/res/drawable-xhdpi/pointer_wait_55.png
new file mode 100644
index 0000000..76e1bb6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_55.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_56.png b/core/res/res/drawable-xhdpi/pointer_wait_56.png
new file mode 100644
index 0000000..c4f1d12
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_56.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_57.png b/core/res/res/drawable-xhdpi/pointer_wait_57.png
new file mode 100644
index 0000000..46ab8e2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_57.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_58.png b/core/res/res/drawable-xhdpi/pointer_wait_58.png
new file mode 100644
index 0000000..9d36957
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_58.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_59.png b/core/res/res/drawable-xhdpi/pointer_wait_59.png
new file mode 100644
index 0000000..c0f342b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_59.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_6.png b/core/res/res/drawable-xhdpi/pointer_wait_6.png
index f7c71b9..ad5ecb4 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_6.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_6.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_60.png b/core/res/res/drawable-xhdpi/pointer_wait_60.png
new file mode 100644
index 0000000..202c512
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_60.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_61.png b/core/res/res/drawable-xhdpi/pointer_wait_61.png
new file mode 100644
index 0000000..c48c3f2
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_61.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_62.png b/core/res/res/drawable-xhdpi/pointer_wait_62.png
new file mode 100644
index 0000000..80dee22
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_62.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_63.png b/core/res/res/drawable-xhdpi/pointer_wait_63.png
new file mode 100644
index 0000000..d5b0ee5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_63.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_64.png b/core/res/res/drawable-xhdpi/pointer_wait_64.png
new file mode 100644
index 0000000..b327a57
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_64.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_65.png b/core/res/res/drawable-xhdpi/pointer_wait_65.png
new file mode 100644
index 0000000..92d832b
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_65.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_66.png b/core/res/res/drawable-xhdpi/pointer_wait_66.png
new file mode 100644
index 0000000..253b8a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_66.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_67.png b/core/res/res/drawable-xhdpi/pointer_wait_67.png
new file mode 100644
index 0000000..a8b3e4e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_67.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_68.png b/core/res/res/drawable-xhdpi/pointer_wait_68.png
new file mode 100644
index 0000000..cfd0343
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_68.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_69.png b/core/res/res/drawable-xhdpi/pointer_wait_69.png
new file mode 100644
index 0000000..cb01ee5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_69.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_7.png b/core/res/res/drawable-xhdpi/pointer_wait_7.png
index 3fa202e..bca504e 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_7.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_7.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_70.png b/core/res/res/drawable-xhdpi/pointer_wait_70.png
new file mode 100644
index 0000000..f38082f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_70.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_71.png b/core/res/res/drawable-xhdpi/pointer_wait_71.png
new file mode 100644
index 0000000..e8b56db
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_71.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_72.png b/core/res/res/drawable-xhdpi/pointer_wait_72.png
new file mode 100644
index 0000000..9dff9e0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_72.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_73.png b/core/res/res/drawable-xhdpi/pointer_wait_73.png
new file mode 100644
index 0000000..e989fcc
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_73.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_74.png b/core/res/res/drawable-xhdpi/pointer_wait_74.png
new file mode 100644
index 0000000..953bbd3
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_74.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_75.png b/core/res/res/drawable-xhdpi/pointer_wait_75.png
new file mode 100644
index 0000000..7df2d9e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_75.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_76.png b/core/res/res/drawable-xhdpi/pointer_wait_76.png
new file mode 100644
index 0000000..b241ef6
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_76.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_77.png b/core/res/res/drawable-xhdpi/pointer_wait_77.png
new file mode 100644
index 0000000..f5018b8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_77.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_78.png b/core/res/res/drawable-xhdpi/pointer_wait_78.png
new file mode 100644
index 0000000..06209a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_78.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_79.png b/core/res/res/drawable-xhdpi/pointer_wait_79.png
new file mode 100644
index 0000000..37887b8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_79.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_8.png b/core/res/res/drawable-xhdpi/pointer_wait_8.png
index e0e50ae..0844944 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_8.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_8.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_80.png b/core/res/res/drawable-xhdpi/pointer_wait_80.png
new file mode 100644
index 0000000..115ef9d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/pointer_wait_80.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_wait_9.png b/core/res/res/drawable-xhdpi/pointer_wait_9.png
index e3de26f..bf6d022 100644
--- a/core/res/res/drawable-xhdpi/pointer_wait_9.png
+++ b/core/res/res/drawable-xhdpi/pointer_wait_9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in.png b/core/res/res/drawable-xhdpi/pointer_zoom_in.png
index 9c29fcb..d436387 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_in.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png b/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png
index beabbd2..e451a33 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out.png b/core/res/res/drawable-xhdpi/pointer_zoom_out.png
index 710552b..afaaedc 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_out.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png b/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png
index b748f07..0ed8327 100644
--- a/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_alias.png b/core/res/res/drawable-xxhdpi/pointer_alias.png
new file mode 100644
index 0000000..c329002
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_alias.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
new file mode 100644
index 0000000..808143a
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_arrow.png b/core/res/res/drawable-xxhdpi/pointer_arrow.png
index 4031f16..56dfedc 100644
--- a/core/res/res/drawable-xxhdpi/pointer_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_cell.png b/core/res/res/drawable-xxhdpi/pointer_cell.png
new file mode 100644
index 0000000..4180882
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_cell.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_context_menu.png b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
new file mode 100644
index 0000000..6ebfaab
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_copy.png b/core/res/res/drawable-xxhdpi/pointer_copy.png
new file mode 100644
index 0000000..bef4fb4
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_copy.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_crosshair.png b/core/res/res/drawable-xxhdpi/pointer_crosshair.png
new file mode 100644
index 0000000..6cb8b83
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_crosshair.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_grab.png b/core/res/res/drawable-xxhdpi/pointer_grab.png
new file mode 100644
index 0000000..6caa1ba
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_grab.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_grabbing.png b/core/res/res/drawable-xxhdpi/pointer_grabbing.png
new file mode 100644
index 0000000..b52f751
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_grabbing.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_hand.png b/core/res/res/drawable-xxhdpi/pointer_hand.png
new file mode 100644
index 0000000..f3ee077
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_hand.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_help.png b/core/res/res/drawable-xxhdpi/pointer_help.png
new file mode 100644
index 0000000..96b2a71
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
new file mode 100644
index 0000000..677ccad
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_nodrop.png b/core/res/res/drawable-xxhdpi/pointer_nodrop.png
new file mode 100644
index 0000000..ef54301
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_nodrop.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png b/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png
new file mode 100644
index 0000000..5b46ce5
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_hover.png b/core/res/res/drawable-xxhdpi/pointer_spot_hover.png
new file mode 100644
index 0000000..6c1ab15
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_touch.png b/core/res/res/drawable-xxhdpi/pointer_spot_touch.png
new file mode 100644
index 0000000..ab69fd8
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_text.png b/core/res/res/drawable-xxhdpi/pointer_text.png
new file mode 100644
index 0000000..0145886
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_text.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
new file mode 100644
index 0000000..e01aa64
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
new file mode 100644
index 0000000..e947e0e
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
new file mode 100644
index 0000000..c867247
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_text.png b/core/res/res/drawable-xxhdpi/pointer_vertical_text.png
new file mode 100644
index 0000000..77ff389
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_text.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_zoom_in.png b/core/res/res/drawable-xxhdpi/pointer_zoom_in.png
new file mode 100644
index 0000000..7852abe
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_zoom_out.png b/core/res/res/drawable-xxhdpi/pointer_zoom_out.png
new file mode 100644
index 0000000..83c621e
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/pointer_zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_alias_icon.xml b/core/res/res/drawable/pointer_alias_icon.xml
index 8ba9301..4c701c0 100644
--- a/core/res/res/drawable/pointer_alias_icon.xml
+++ b/core/res/res/drawable/pointer_alias_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_alias"
- android:hotSpotX="8dp"
- android:hotSpotY="6dp" />
+ android:hotSpotX="11.5dp"
+ android:hotSpotY="11.5dp" />
diff --git a/core/res/res/drawable/pointer_alias_large_icon.xml b/core/res/res/drawable/pointer_alias_large_icon.xml
index 149f58c..8dca8a5 100644
--- a/core/res/res/drawable/pointer_alias_large_icon.xml
+++ b/core/res/res/drawable/pointer_alias_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_alias_large"
- android:hotSpotX="19dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="28.75dp"
+ android:hotSpotY="28.75dp" />
diff --git a/core/res/res/drawable/pointer_all_scroll_icon.xml b/core/res/res/drawable/pointer_all_scroll_icon.xml
index e946948..a0cf318 100644
--- a/core/res/res/drawable/pointer_all_scroll_icon.xml
+++ b/core/res/res/drawable/pointer_all_scroll_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_all_scroll"
- android:hotSpotX="11dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_all_scroll_large_icon.xml b/core/res/res/drawable/pointer_all_scroll_large_icon.xml
index c580f76..1fe7dad 100644
--- a/core/res/res/drawable/pointer_all_scroll_large_icon.xml
+++ b/core/res/res/drawable/pointer_all_scroll_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_all_scroll_large"
- android:hotSpotX="32dp"
- android:hotSpotY="31dp" />
+ android:hotSpotX="30dp"
+ android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_arrow_icon.xml b/core/res/res/drawable/pointer_arrow_icon.xml
index 72af0c1..61eb1cd 100644
--- a/core/res/res/drawable/pointer_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_arrow_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_arrow"
- android:hotSpotX="5dp"
- android:hotSpotY="5dp" />
+ android:hotSpotX="4.5dp"
+ android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_arrow_large_icon.xml b/core/res/res/drawable/pointer_arrow_large_icon.xml
index 22c7bfe..0f2997b 100644
--- a/core/res/res/drawable/pointer_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_arrow_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_arrow_large"
- android:hotSpotX="10dp"
- android:hotSpotY="10dp" />
+ android:hotSpotX="13.5dp"
+ android:hotSpotY="10.5dp" />
diff --git a/core/res/res/drawable/pointer_cell_icon.xml b/core/res/res/drawable/pointer_cell_icon.xml
index da1e320..35a61c2 100644
--- a/core/res/res/drawable/pointer_cell_icon.xml
+++ b/core/res/res/drawable/pointer_cell_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_cell"
- android:hotSpotX="11dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_context_menu_icon.xml b/core/res/res/drawable/pointer_context_menu_icon.xml
index 330b627..badc736 100644
--- a/core/res/res/drawable/pointer_context_menu_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_context_menu"
- android:hotSpotX="4dp"
- android:hotSpotY="4dp" />
+ android:hotSpotX="4.5dp"
+ android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_context_menu_large_icon.xml b/core/res/res/drawable/pointer_context_menu_large_icon.xml
index c57e615..e07e5b6 100644
--- a/core/res/res/drawable/pointer_context_menu_large_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_context_menu_large"
- android:hotSpotX="11dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="13.5dp"
+ android:hotSpotY="10.5dp" />
diff --git a/core/res/res/drawable/pointer_copy_icon.xml b/core/res/res/drawable/pointer_copy_icon.xml
index e299db5..da32939 100644
--- a/core/res/res/drawable/pointer_copy_icon.xml
+++ b/core/res/res/drawable/pointer_copy_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_copy"
- android:hotSpotX="9dp"
- android:hotSpotY="9dp" />
+ android:hotSpotX="7.5dp"
+ android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_copy_large_icon.xml b/core/res/res/drawable/pointer_copy_large_icon.xml
index 4ee3f18..55d47b4 100644
--- a/core/res/res/drawable/pointer_copy_large_icon.xml
+++ b/core/res/res/drawable/pointer_copy_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_copy_large"
- android:hotSpotX="10dp"
- android:hotSpotY="10dp" />
+ android:hotSpotX="22.5dp"
+ android:hotSpotY="22.5dp" />
diff --git a/core/res/res/drawable/pointer_crosshair_large_icon.xml b/core/res/res/drawable/pointer_crosshair_large_icon.xml
index 6a71b7b..1073fff 100644
--- a/core/res/res/drawable/pointer_crosshair_large_icon.xml
+++ b/core/res/res/drawable/pointer_crosshair_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_crosshair_large"
- android:hotSpotX="31dp"
+ android:hotSpotX="30dp"
android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_grab_icon.xml b/core/res/res/drawable/pointer_grab_icon.xml
index d437b3a..b3d4e78 100644
--- a/core/res/res/drawable/pointer_grab_icon.xml
+++ b/core/res/res/drawable/pointer_grab_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grab"
- android:hotSpotX="8dp"
- android:hotSpotY="5dp" />
+ android:hotSpotX="13.5dp"
+ android:hotSpotY="13.5dp" />
diff --git a/core/res/res/drawable/pointer_grab_large_icon.xml b/core/res/res/drawable/pointer_grab_large_icon.xml
index f2df1cb..343c7d2 100644
--- a/core/res/res/drawable/pointer_grab_large_icon.xml
+++ b/core/res/res/drawable/pointer_grab_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grab_large"
- android:hotSpotX="21dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="33.75dp"
+ android:hotSpotY="33.75dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_icon.xml b/core/res/res/drawable/pointer_grabbing_icon.xml
index 38f4c3a..93818d7 100644
--- a/core/res/res/drawable/pointer_grabbing_icon.xml
+++ b/core/res/res/drawable/pointer_grabbing_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grabbing"
- android:hotSpotX="9dp"
- android:hotSpotY="9dp" />
+ android:hotSpotX="8.5dp"
+ android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_large_icon.xml b/core/res/res/drawable/pointer_grabbing_large_icon.xml
index e4042bf..ac16265 100644
--- a/core/res/res/drawable/pointer_grabbing_large_icon.xml
+++ b/core/res/res/drawable/pointer_grabbing_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_grabbing_large"
- android:hotSpotX="20dp"
- android:hotSpotY="12dp" />
+ android:hotSpotX="25.5dp"
+ android:hotSpotY="22.5dp" />
diff --git a/core/res/res/drawable/pointer_hand_icon.xml b/core/res/res/drawable/pointer_hand_icon.xml
index 3d90b88..3f9d1a6 100644
--- a/core/res/res/drawable/pointer_hand_icon.xml
+++ b/core/res/res/drawable/pointer_hand_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_hand"
- android:hotSpotX="9dp"
- android:hotSpotY="4dp" />
+ android:hotSpotX="9.5dp"
+ android:hotSpotY="1.5dp" />
diff --git a/core/res/res/drawable/pointer_hand_large_icon.xml b/core/res/res/drawable/pointer_hand_large_icon.xml
index e34422a..cd49762 100644
--- a/core/res/res/drawable/pointer_hand_large_icon.xml
+++ b/core/res/res/drawable/pointer_hand_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_hand_large"
- android:hotSpotX="25dp"
- android:hotSpotY="7dp" />
+ android:hotSpotX="23.75dp"
+ android:hotSpotY="3.75dp" />
diff --git a/core/res/res/drawable/pointer_help_icon.xml b/core/res/res/drawable/pointer_help_icon.xml
index 49ae554..0a4bdb0 100644
--- a/core/res/res/drawable/pointer_help_icon.xml
+++ b/core/res/res/drawable/pointer_help_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_help"
- android:hotSpotX="4dp"
- android:hotSpotY="4dp" />
+ android:hotSpotX="4.5dp"
+ android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_help_large_icon.xml b/core/res/res/drawable/pointer_help_large_icon.xml
index 4c60a55..43a1261 100644
--- a/core/res/res/drawable/pointer_help_large_icon.xml
+++ b/core/res/res/drawable/pointer_help_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_help_large"
- android:hotSpotX="10dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="13.5dp"
+ android:hotSpotY="10.5dp" />
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
index 93b3fe5..6ddcc42 100644
--- a/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_horizontal_double_arrow"
android:hotSpotX="12dp"
- android:hotSpotY="11dp" />
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml
index a2039e6..854be26 100644
--- a/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_horizontal_double_arrow_large"
- android:hotSpotX="35dp"
- android:hotSpotY="29dp" />
+ android:hotSpotX="30dp"
+ android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_icon.xml b/core/res/res/drawable/pointer_nodrop_icon.xml
index 955b40f..4dffd23 100644
--- a/core/res/res/drawable/pointer_nodrop_icon.xml
+++ b/core/res/res/drawable/pointer_nodrop_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_nodrop"
- android:hotSpotX="9dp"
- android:hotSpotY="9dp" />
+ android:hotSpotX="7.5dp"
+ android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_large_icon.xml b/core/res/res/drawable/pointer_nodrop_large_icon.xml
index cde2e41..602c744 100644
--- a/core/res/res/drawable/pointer_nodrop_large_icon.xml
+++ b/core/res/res/drawable/pointer_nodrop_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_nodrop_large"
- android:hotSpotX="10dp"
- android:hotSpotY="10dp" />
+ android:hotSpotX="22.5dp"
+ android:hotSpotY="22.5dp" />
diff --git a/core/res/res/drawable/pointer_spot_anchor_icon.xml b/core/res/res/drawable/pointer_spot_anchor_icon.xml
index 73c0c11..b7d000f 100644
--- a/core/res/res/drawable/pointer_spot_anchor_icon.xml
+++ b/core/res/res/drawable/pointer_spot_anchor_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_spot_anchor"
- android:hotSpotX="22dp"
- android:hotSpotY="22dp" />
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_spot_hover_icon.xml b/core/res/res/drawable/pointer_spot_hover_icon.xml
index 1d7440b..0d641b2 100644
--- a/core/res/res/drawable/pointer_spot_hover_icon.xml
+++ b/core/res/res/drawable/pointer_spot_hover_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_spot_hover"
- android:hotSpotX="22dp"
- android:hotSpotY="22dp" />
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_spot_touch_icon.xml b/core/res/res/drawable/pointer_spot_touch_icon.xml
index f4f0639..b1c8c0b 100644
--- a/core/res/res/drawable/pointer_spot_touch_icon.xml
+++ b/core/res/res/drawable/pointer_spot_touch_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_spot_touch"
- android:hotSpotX="16dp"
- android:hotSpotY="16dp" />
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_text_icon.xml b/core/res/res/drawable/pointer_text_icon.xml
index d948c89..1daba65 100644
--- a/core/res/res/drawable/pointer_text_icon.xml
+++ b/core/res/res/drawable/pointer_text_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_text"
android:hotSpotX="12dp"
- android:hotSpotY="12dp" />
+ android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_text_large_icon.xml b/core/res/res/drawable/pointer_text_large_icon.xml
index 24d35b0..5b32ea34 100644
--- a/core/res/res/drawable/pointer_text_large_icon.xml
+++ b/core/res/res/drawable/pointer_text_large_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_text_large"
android:hotSpotX="30dp"
- android:hotSpotY="32dp" />
+ android:hotSpotY="27.5dp" />
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
index de5efe2..1598766e 100644
--- a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow"
- android:hotSpotX="11dp"
- android:hotSpotY="11dp" />
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml
index 270ccc9..5e0d37b 100644
--- a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow_large"
- android:hotSpotX="32dp"
+ android:hotSpotX="30dp"
android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
index e87b526..83324ef9 100644
--- a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow"
android:hotSpotX="12dp"
- android:hotSpotY="11dp" />
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml
index e350a43..6ed5a0b 100644
--- a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow_large"
- android:hotSpotX="32dp"
- android:hotSpotY="31dp" />
+ android:hotSpotX="30dp"
+ android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
index 5759079..c746fd8 100644
--- a/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_vertical_double_arrow"
- android:hotSpotX="11dp"
+ android:hotSpotX="12dp"
android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml
index 65728ad..190d181 100644
--- a/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_vertical_double_arrow_large"
- android:hotSpotX="29dp"
- android:hotSpotY="32dp" />
+ android:hotSpotX="30dp"
+ android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_vertical_text_icon.xml b/core/res/res/drawable/pointer_vertical_text_icon.xml
index 3b48dc8..275b979 100644
--- a/core/res/res/drawable/pointer_vertical_text_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_text_icon.xml
@@ -2,4 +2,4 @@
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_vertical_text"
android:hotSpotX="12dp"
- android:hotSpotY="11dp" />
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_text_large_icon.xml b/core/res/res/drawable/pointer_vertical_text_large_icon.xml
index 48211cb..a1b5dc2 100644
--- a/core/res/res/drawable/pointer_vertical_text_large_icon.xml
+++ b/core/res/res/drawable/pointer_vertical_text_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_vertical_text_large"
- android:hotSpotX="32dp"
+ android:hotSpotX="30dp"
android:hotSpotY="30dp" />
diff --git a/core/res/res/drawable/pointer_wait.xml b/core/res/res/drawable/pointer_wait.xml
index 8955ce8..c769be5 100644
--- a/core/res/res/drawable/pointer_wait.xml
+++ b/core/res/res/drawable/pointer_wait.xml
@@ -1,38 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
- <item android:drawable="@drawable/pointer_wait_1" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_2" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_3" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_4" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_5" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_6" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_7" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_8" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_9" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_10" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_11" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_12" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_13" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_14" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_15" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_16" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_17" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_18" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_19" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_20" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_21" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_22" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_23" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_24" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_25" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_26" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_27" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_28" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_29" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_30" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_31" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_32" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_33" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_34" android:duration="25"/>
- <item android:drawable="@drawable/pointer_wait_35" android:duration="25"/>
+ <item android:drawable="@drawable/pointer_wait_0" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_1" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_2" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_3" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_4" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_5" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_6" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_7" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_8" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_9" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_10" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_11" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_12" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_13" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_14" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_15" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_16" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_17" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_18" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_19" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_20" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_21" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_22" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_23" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_24" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_25" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_26" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_27" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_28" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_29" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_30" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_31" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_32" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_33" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_34" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_35" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_36" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_37" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_38" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_39" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_40" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_41" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_42" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_43" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_44" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_45" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_46" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_47" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_48" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_49" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_50" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_51" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_52" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_53" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_54" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_55" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_56" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_57" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_58" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_59" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_60" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_61" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_62" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_63" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_64" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_65" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_66" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_67" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_68" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_69" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_70" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_71" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_72" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_73" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_74" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_75" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_76" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_77" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_78" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_79" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_80" android:duration="16"/>
</animation-list>
diff --git a/core/res/res/drawable/pointer_wait_icon.xml b/core/res/res/drawable/pointer_wait_icon.xml
index d9b03b0..e52c4e0 100644
--- a/core/res/res/drawable/pointer_wait_icon.xml
+++ b/core/res/res/drawable/pointer_wait_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_wait"
- android:hotSpotX="7dp"
- android:hotSpotY="7dp" />
+ android:hotSpotX="8dp"
+ android:hotSpotY="8dp" />
diff --git a/core/res/res/drawable/pointer_zoom_in_icon.xml b/core/res/res/drawable/pointer_zoom_in_icon.xml
index e2dcb49..322fab8 100644
--- a/core/res/res/drawable/pointer_zoom_in_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_in_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_zoom_in"
- android:hotSpotX="10dp"
- android:hotSpotY="10dp" />
+ android:hotSpotX="10.5dp"
+ android:hotSpotY="9.5dp" />
diff --git a/core/res/res/drawable/pointer_zoom_in_large_icon.xml b/core/res/res/drawable/pointer_zoom_in_large_icon.xml
index 3eb89f56..4505892 100644
--- a/core/res/res/drawable/pointer_zoom_in_large_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_in_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_zoom_in_large"
- android:hotSpotX="25dp"
- android:hotSpotY="26dp" />
+ android:hotSpotX="26.25dp"
+ android:hotSpotY="23.75dp" />
diff --git a/core/res/res/drawable/pointer_zoom_out_icon.xml b/core/res/res/drawable/pointer_zoom_out_icon.xml
index b805df3..bfb5aa6 100644
--- a/core/res/res/drawable/pointer_zoom_out_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_out_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_zoom_out"
- android:hotSpotX="10dp"
- android:hotSpotY="10dp" />
+ android:hotSpotX="10.5dp"
+ android:hotSpotY="9.5dp" />
diff --git a/core/res/res/drawable/pointer_zoom_out_large_icon.xml b/core/res/res/drawable/pointer_zoom_out_large_icon.xml
index df6412e..90057e6 100644
--- a/core/res/res/drawable/pointer_zoom_out_large_icon.xml
+++ b/core/res/res/drawable/pointer_zoom_out_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_zoom_out_large"
- android:hotSpotX="26dp"
- android:hotSpotY="26dp" />
+ android:hotSpotX="26.25dp"
+ android:hotSpotY="23.75dp" />
diff --git a/core/res/res/layout-watch/app_anr_dialog.xml b/core/res/res/layout-watch/app_anr_dialog.xml
new file mode 100644
index 0000000..f9605af
--- /dev/null
+++ b/core/res/res/layout-watch/app_anr_dialog.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:showDividers="middle"
+ android:divider="@drawable/global_action_item_divider">
+ <Button
+ android:id="@+id/aerr_close"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_close_app"
+ android:drawableStart="@drawable/ic_close"
+ style="@style/aerr_list_item"/>
+ <Button
+ android:id="@+id/aerr_wait"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_wait"
+ android:drawableStart="@drawable/ic_schedule"
+ style="@style/aerr_list_item"/>
+ <Button
+ android:id="@+id/aerr_report"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_report"
+ android:drawableStart="@drawable/ic_feedback"
+ style="@style/aerr_list_item"/>
+</LinearLayout>
diff --git a/core/res/res/layout-watch/app_error_dialog.xml b/core/res/res/layout-watch/app_error_dialog.xml
new file mode 100644
index 0000000..8857b5f
--- /dev/null
+++ b/core/res/res/layout-watch/app_error_dialog.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:showDividers="middle"
+ android:divider="@drawable/global_action_item_divider">
+ <Button
+ android:id="@+id/aerr_restart"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_restart"
+ android:drawableStart="@drawable/ic_refresh"
+ style="@style/aerr_list_item" />
+ <Button
+ android:id="@+id/aerr_app_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_info"
+ android:drawableStart="@drawable/ic_info_outline_24"
+ style="@style/aerr_list_item" />
+ <Button
+ android:id="@+id/aerr_close"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_close_app"
+ android:drawableStart="@drawable/ic_close"
+ style="@style/aerr_list_item" />
+ <Button
+ android:id="@+id/aerr_report"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_report"
+ android:drawableStart="@drawable/ic_feedback"
+ style="@style/aerr_list_item" />
+ <Button
+ android:id="@+id/aerr_mute"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/aerr_mute"
+ android:drawableStart="@drawable/ic_eject_24dp"
+ style="@style/aerr_list_item" />
+</LinearLayout>
diff --git a/core/res/res/layout-watch/watch_base_error_dialog.xml b/core/res/res/layout-watch/watch_base_error_dialog.xml
new file mode 100644
index 0000000..0f3fb42
--- /dev/null
+++ b/core/res/res/layout-watch/watch_base_error_dialog.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<com.android.internal.widget.WatchListDecorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ScrollView
+ android:id="@+id/scrollView"
+ android:fillViewport="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="?dialogPreferredPadding"
+ android:paddingRight="?dialogPreferredPadding"
+ android:paddingTop="@dimen/base_error_dialog_top_padding"
+ android:paddingBottom="@dimen/base_error_dialog_bottom_padding"
+ android:orientation="vertical" >
+ <!-- Top Panel -->
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/topPanel">
+ <include android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/watch_base_error_dialog_title"/>
+ </FrameLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="16dp">
+ </FrameLayout>
+ <!-- Content Panel -->
+ <FrameLayout
+ android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal|top"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"/>
+ </FrameLayout>
+ <!-- Custom Panel, to replace content panel if needed -->
+ <FrameLayout
+ android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minHeight="64dp">
+ <FrameLayout
+ android:id="@+android:id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ <!-- Button Panel -->
+ <FrameLayout
+ android:id="@+id/buttonPanel"
+ android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:orientation="vertical"
+ style="?android:attr/buttonBarStyle"
+ android:measureWithLargestChild="true">
+ <Button
+ android:id="@+id/button1"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/button3"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/button2"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ </FrameLayout>
+ </LinearLayout>
+ </ScrollView>
+</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout-watch/watch_base_error_dialog_title.xml b/core/res/res/layout-watch/watch_base_error_dialog_title.xml
new file mode 100644
index 0000000..aa14c08
--- /dev/null
+++ b/core/res/res/layout-watch/watch_base_error_dialog_title.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/base_error_dialog_contents_padding"
+ android:paddingRight="@dimen/base_error_dialog_contents_padding"
+ android:orientation="vertical"
+ android:gravity="top|center_horizontal">
+ <FrameLayout
+ android:adjustViewBounds="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/icon"
+ android:adjustViewBounds="true"
+ android:maxHeight="24dp"
+ android:maxWidth="24dp"
+ android:layout_marginTop="@dimen/screen_percentage_10"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@null"/>
+ </FrameLayout>
+ <TextView
+ android:id="@+id/alertTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="38dp"
+ android:textAppearance="@style/TextAppearance.Watch.BaseErrorDialog.Title"
+ android:maxLines="3"
+ android:gravity="center_horizontal|top"/>
+</LinearLayout>
diff --git a/core/res/res/values-w180dp-notround-watch/dimens.xml b/core/res/res/values-w180dp-notround-watch/dimens.xml
new file mode 100644
index 0000000..5887661
--- /dev/null
+++ b/core/res/res/values-w180dp-notround-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<resources>
+ <!-- 14.4% of display size -->
+ <dimen name="base_error_dialog_top_padding">26dp</dimen>
+ <!-- 2.8% of display size -->
+ <dimen name="base_error_dialog_padding">5dp</dimen>
+ <!-- 35.56% of display size -->
+ <dimen name="base_error_dialog_bottom_padding">64dp</dimen>
+</resources>
diff --git a/core/res/res/values-w192dp-round-watch/dimens.xml b/core/res/res/values-w192dp-round-watch/dimens.xml
new file mode 100644
index 0000000..5aed20e
--- /dev/null
+++ b/core/res/res/values-w192dp-round-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<resources>
+ <!-- 16.7% of display size -->
+ <dimen name="base_error_dialog_top_padding">32dp</dimen>
+ <!-- 5.2% of display size -->
+ <dimen name="base_error_dialog_padding">10dp</dimen>
+ <!-- 20.83% of display size -->
+ <dimen name="base_error_dialog_bottom_padding">40dp</dimen>
+</resources>
diff --git a/core/res/res/values-w213dp-round-watch/dimens.xml b/core/res/res/values-w213dp-round-watch/dimens.xml
new file mode 100644
index 0000000..27fff75
--- /dev/null
+++ b/core/res/res/values-w213dp-round-watch/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<resources>
+ <!-- 16.7% of display size -->
+ <dimen name="base_error_dialog_top_padding">36dp</dimen>
+ <!-- 5.2% of display size -->
+ <dimen name="base_error_dialog_padding">11dp</dimen>
+ <!-- 36.46% of display size -->
+ <dimen name="base_error_dialog_bottom_padding">78dp</dimen>
+</resources>
diff --git a/core/res/res/values-watch/dimens.xml b/core/res/res/values-watch/dimens.xml
index 5472316..c7caa39 100644
--- a/core/res/res/values-watch/dimens.xml
+++ b/core/res/res/values-watch/dimens.xml
@@ -20,4 +20,9 @@
<dimen name="alert_dialog_button_bar_height">0dp</dimen>
<dimen name="toast_y_offset">0dip</dimen>
+
+ <!-- AppErrorDialog's list item height -->
+ <dimen name="aerr_list_item_height">52dp</dimen>
+ <!-- Padding for contents in a view of BaseErrorDialog such as a title and buttons -->
+ <dimen name="base_error_dialog_contents_padding">14dp</dimen>
</resources>
diff --git a/core/res/res/values-watch/styles.xml b/core/res/res/values-watch/styles.xml
index 3172f73..6e84f39 100644
--- a/core/res/res/values-watch/styles.xml
+++ b/core/res/res/values-watch/styles.xml
@@ -19,4 +19,43 @@
<item name="fontFamily">sans-serif-regular</item>
<item name="textSize">13sp</item>
</style>
+
+ <!-- @hide -->
+ <style name="TextAppearance.Watch"/>
+
+ <!-- @hide -->
+ <style name="TextAppearance.Watch.BaseErrorDialog">
+ <item name="fontFamily">google-sans-text-medium</item>
+ <item name="textColor">@android:color/white</item>
+ <item name="textAllCaps">false</item>
+ </style>
+
+ <!-- @hide -->
+ <style name="TextAppearance.Watch.BaseErrorDialog.Title">
+ <item name="textSize">16sp</item>
+ <item name="letterSpacing">0.024</item>
+ </style>
+
+ <!-- @hide -->
+ <style name="TextAppearance.Watch.AppErrorDialog"
+ parent="TextAppearance.Watch.BaseErrorDialog"/>
+
+ <!-- @hide -->
+ <style name="TextAppearance.Watch.AppErrorDialog.Item">
+ <item name="textSize">15sp</item>
+ <item name="letterSpacing">0.01</item>
+ </style>
+
+ <!-- @hide -->
+ <style name="aerr_list_item">
+ <item name="minHeight">@dimen/aerr_list_item_height</item>
+ <item name="textAppearance">@style/TextAppearance.Watch.AppErrorDialog.Item</item>
+ <item name="gravity">center_vertical</item>
+ <item name="paddingStart">@dimen/base_error_dialog_contents_padding</item>
+ <item name="paddingEnd">@dimen/base_error_dialog_contents_padding</item>
+ <item name="background">@drawable/global_actions_item_grey_background</item>
+ <item name="drawablePadding">6dp</item>
+ <item name="drawableTint">@android:color/white</item>
+ <item name="drawableTintMode">src_atop</item>
+ </style>
</resources>
diff --git a/core/res/res/values-watch/styles_device_default.xml b/core/res/res/values-watch/styles_device_default.xml
index e2261af..8a2ce5d 100644
--- a/core/res/res/values-watch/styles_device_default.xml
+++ b/core/res/res/values-watch/styles_device_default.xml
@@ -34,4 +34,7 @@
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">google-sans-medium</item>
</style>
+ <style name="BaseErrorDialog.DeviceDefault" parent="AlertDialog.DeviceDefault">
+ <item name="layout">@layout/watch_base_error_dialog</item>
+ </style>
</resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 1db006f..c4c1ed9 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -427,6 +427,8 @@
<!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. -->
<style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert">
+ <item name="alertDialogStyle">@style/BaseErrorDialog.DeviceDefault</item>
+ <item name="dialogPreferredPadding">@dimen/base_error_dialog_padding</item>
<item name="windowContentTransitions">false</item>
<item name="windowActivityTransitions">false</item>
<item name="windowCloseOnTouchOutside">false</item>
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 180a312..bf8c7c6 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -108,8 +108,10 @@
Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"),
Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/wm_log.winscope"),
Paths.get("/data/misc/wmtrace/layers_trace.winscope"),
Paths.get("/data/misc/wmtrace/transactions_trace.winscope"),
+ Paths.get("/data/misc/wmtrace/transition_trace.winscope"),
};
private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = {
Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"),
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index bf8ca8b..4cccf8e 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -76,7 +76,8 @@
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
+ <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 3b8b8c7..4a12bb3 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -142,6 +142,31 @@
}
@Test
+ public void testGetHdrCapabilities_getSupportedHdrTypes_returns_mode_specific_hdr_types() {
+ setDisplayInfoPortrait(mDisplayInfo);
+ float[] alternativeRefreshRates = new float[0];
+ int[] hdrTypesWithDv = new int[] {1, 2, 3, 4};
+ Display.Mode modeWithDv = new Display.Mode(/* modeId= */ 0, 0, 0, 0f,
+ alternativeRefreshRates, hdrTypesWithDv);
+
+ int[] hdrTypesWithoutDv = new int[]{2, 3, 4};
+ Display.Mode modeWithoutDv = new Display.Mode(/* modeId= */ 1, 0, 0, 0f,
+ alternativeRefreshRates, hdrTypesWithoutDv);
+
+ mDisplayInfo.supportedModes = new Display.Mode[] {modeWithoutDv, modeWithDv};
+ mDisplayInfo.hdrCapabilities = new Display.HdrCapabilities(hdrTypesWithDv, 0, 0, 0);
+
+ final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+
+ mDisplayInfo.modeId = 0;
+ assertArrayEquals(hdrTypesWithDv, display.getHdrCapabilities().getSupportedHdrTypes());
+
+ mDisplayInfo.modeId = 1;
+ assertArrayEquals(hdrTypesWithoutDv, display.getHdrCapabilities().getSupportedHdrTypes());
+ }
+
+ @Test
public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() {
setDisplayInfoPortrait(mDisplayInfo);
final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo,
diff --git a/graphics/java/android/graphics/HardwareBufferRenderer.java b/graphics/java/android/graphics/HardwareBufferRenderer.java
index 361dc59..e04f13c 100644
--- a/graphics/java/android/graphics/HardwareBufferRenderer.java
+++ b/graphics/java/android/graphics/HardwareBufferRenderer.java
@@ -275,11 +275,22 @@
Consumer<RenderResult> wrapped = consumable -> executor.execute(
() -> renderCallback.accept(consumable));
if (!isClosed()) {
+ int renderWidth;
+ int renderHeight;
+ if (mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
+ || mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270) {
+ renderWidth = mHardwareBuffer.getHeight();
+ renderHeight = mHardwareBuffer.getWidth();
+ } else {
+ renderWidth = mHardwareBuffer.getWidth();
+ renderHeight = mHardwareBuffer.getHeight();
+ }
+
nRender(
mProxy,
mTransform,
- mHardwareBuffer.getWidth(),
- mHardwareBuffer.getHeight(),
+ renderWidth,
+ renderHeight,
mColorSpace.getNativeInstance(),
wrapped);
} else {
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 563fb4d..87a7c3e 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -228,9 +228,10 @@
the screen. This time the double-tap can happen on the top or bottom of the screen.
To teach the user about this feature, we display an education explaining how the double-tap
works and how the app can be moved on the screen.
- This is the text we show to the user below an animated icon visualizing the double-tap
- action. [CHAR LIMIT=NONE] -->
- <string name="letterbox_reachability_reposition_text">Double-tap to move this app</string>
+ This is the text we show to the user below an icon visualizing the double-tap
+ action. The description should be split in two lines separated by "\n" like for this
+ locale. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_reachability_reposition_text">Double-tap to\nmove this app</string>
<!-- Freeform window caption strings -->
<!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 3d1ed87..14daae0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -157,14 +157,21 @@
@WMSingleton
@Provides
static PipTransitionController provideTvPipTransition(
+ Context context,
ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
Transitions transitions,
- PipAnimationController pipAnimationController,
+ TvPipBoundsState tvPipBoundsState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipTransitionState pipTransitionState,
+ TvPipMenuController pipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
- TvPipBoundsState tvPipBoundsState, TvPipMenuController pipMenuController) {
- return new TvPipTransition(shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
- pipMenuController, tvPipBoundsAlgorithm, pipAnimationController);
+ PipAnimationController pipAnimationController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+ return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+ tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ Optional.empty());
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e2cd7a0..d8e2f5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -54,6 +54,7 @@
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -677,13 +678,15 @@
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
- EnterDesktopTaskTransitionHandler transitionHandler,
+ EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
+ ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellController, displayController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
- transitionHandler, desktopModeTaskRepository, mainExecutor);
+ enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+ desktopModeTaskRepository, mainExecutor);
}
@WMSingleton
@@ -695,6 +698,15 @@
@WMSingleton
@Provides
+ static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Context context
+ ) {
+ return new ExitDesktopTaskTransitionHandler(transitions, context);
+ }
+
+ @WMSingleton
+ @Provides
@DynamicOverride
static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
return new DesktopModeTaskRepository();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 015d5c1..fb0a91f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,10 +16,13 @@
package com.android.wm.shell.desktopmode;
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
@@ -32,12 +35,12 @@
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -56,6 +59,9 @@
private final SyncTransactionQueue mSyncQueue;
private SurfaceControlViewHost mViewHost;
+ private View mView;
+ private boolean mIsFullscreen;
+
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
@@ -67,21 +73,19 @@
mTaskSurface = taskSurface;
mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
+ createView();
}
/**
- * Create and animate the indicator for the exit desktop mode transition.
+ * Create a fullscreen indicator with no animation
*/
- public void createFullscreenIndicator() {
+ private void createView() {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mContext.getResources();
final DisplayMetrics metrics = resources.getDisplayMetrics();
final int screenWidth = metrics.widthPixels;
final int screenHeight = metrics.heightPixels;
- final int padding = mDisplayController
- .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
- final ImageView v = new ImageView(mContext);
- v.setImageResource(R.drawable.desktop_windowing_transition_background);
+ mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
mLeash = builder
@@ -101,7 +105,7 @@
mViewHost = new SurfaceControlViewHost(mContext,
mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
"FullscreenVisualIndicator");
- mViewHost.setView(v, lp);
+ mViewHost.setView(mView, lp);
// We want this indicator to be behind the dragged task, but in front of all others.
t.setRelativeLayer(mLeash, mTaskSurface, -1);
@@ -109,17 +113,56 @@
transaction.merge(t);
t.close();
});
- final Rect startBounds = new Rect(padding, padding,
- screenWidth - padding, screenHeight - padding);
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
- startBounds);
+ }
+
+ /**
+ * Create fullscreen indicator and fades it in.
+ */
+ public void createFullscreenIndicator() {
+ mIsFullscreen = true;
+ mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
+ */
+ public void createFullscreenIndicatorWithAnimatedBounds() {
+ mIsFullscreen = true;
+ mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+ .toFullscreenAnimatorWithAnimatedBounds(mView,
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Takes existing fullscreen indicator and animates it to freeform bounds
+ */
+ public void transitionFullscreenIndicatorToFreeform() {
+ mIsFullscreen = false;
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+ animator.start();
+ }
+
+ /**
+ * Takes the existing freeform indicator and animates it to fullscreen
+ */
+ public void transitionFreeformIndicatorToFullscreen() {
+ mIsFullscreen = true;
+ final VisualIndicatorAnimator animator =
+ VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
+ mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
/**
* Release the indicator and its components when it is no longer needed.
*/
- public void releaseFullscreenIndicator() {
+ public void releaseVisualIndicator() {
if (mViewHost == null) return;
if (mViewHost != null) {
mViewHost.release();
@@ -136,20 +179,28 @@
});
}
}
+
+ /**
+ * Returns true if visual indicator is fullscreen
+ */
+ public boolean isFullscreen() {
+ return mIsFullscreen;
+ }
+
/**
* Animator for Desktop Mode transitions which supports bounds and alpha animation.
*/
private static class VisualIndicatorAnimator extends ValueAnimator {
private static final int FULLSCREEN_INDICATOR_DURATION = 200;
- private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
+ private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
private static final float INDICATOR_FINAL_OPACITY = 0.7f;
- private final ImageView mView;
+ private final View mView;
private final Rect mStartBounds;
private final Rect mEndBounds;
private final RectEvaluator mRectEvaluator;
- private VisualIndicatorAnimator(ImageView view, Rect startBounds,
+ private VisualIndicatorAnimator(View view, Rect startBounds,
Rect endBounds) {
mView = view;
mStartBounds = new Rect(startBounds);
@@ -161,30 +212,66 @@
/**
* Create animator for visual indicator of fullscreen transition
*
- * @param view the view for this indicator
- * @param startBounds the starting bounds of the fullscreen indicator
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
*/
- public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
- Rect startBounds) {
- view.getDrawable().setBounds(startBounds);
- int width = startBounds.width();
- int height = startBounds.height();
- Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
- (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
- (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
- (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
- VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
+ public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
+ @NonNull DisplayLayout displayLayout) {
+ final Rect bounds = getMaxBounds(displayLayout);
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, bounds, bounds);
animator.setInterpolator(new DecelerateInterpolator());
- setupFullscreenIndicatorAnimation(animator);
+ setupIndicatorAnimation(animator);
+ return animator;
+ }
+
+
+ /**
+ * Create animator for visual indicator of fullscreen transition
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
+ */
+ public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
+ @NonNull View view, @NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ Rect startBounds = new Rect(padding, padding,
+ displayLayout.width() - padding, displayLayout.height() - padding);
+ view.getBackground().setBounds(startBounds);
+
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, startBounds, getMaxBounds(displayLayout));
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator);
return animator;
}
/**
- * Add necessary listener for animation of fullscreen indicator
+ * Create animator for visual indicator of freeform transition
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is currently on
*/
- private static void setupFullscreenIndicatorAnimation(
- VisualIndicatorAnimator animator) {
+ public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
+ @NonNull DisplayLayout displayLayout) {
+ final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
+ final int width = displayLayout.width();
+ final int height = displayLayout.height();
+ Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
+ (int) (adjustmentPercentage * height / 2),
+ (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
+ (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, getMaxBounds(displayLayout), endBounds);
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator);
+ return animator;
+ }
+
+ /**
+ * Add necessary listener for animation of indicator
+ */
+ private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
animator.addUpdateListener(a -> {
if (animator.mView != null) {
animator.updateBounds(a.getAnimatedFraction(), animator.mView);
@@ -196,7 +283,7 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- animator.mView.getDrawable().setBounds(animator.mEndBounds);
+ animator.mView.getBackground().setBounds(animator.mEndBounds);
}
});
animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
@@ -210,9 +297,12 @@
* @param fraction fraction to use, compared against previous fraction
* @param view the view to update
*/
- private void updateBounds(float fraction, ImageView view) {
+ private void updateBounds(float fraction, View view) {
+ if (mStartBounds.equals(mEndBounds)) {
+ return;
+ }
Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
- view.getDrawable().setBounds(currentBounds);
+ view.getBackground().setBounds(currentBounds);
}
/**
@@ -223,5 +313,23 @@
private void updateIndicatorAlpha(float fraction, View view) {
view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
}
+
+ /**
+ * Return the max bounds of a fullscreen indicator
+ */
+ private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ final int width = displayLayout.width() - 2 * padding;
+ final int height = displayLayout.height() - 2 * padding;
+ Rect endBounds = new Rect((int) (padding
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+ (int) (padding
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
+ (int) (displayLayout.width() - padding
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+ (int) (displayLayout.height() - padding
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
+ return endBounds;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index cb04a43..670b24c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -68,7 +68,8 @@
private val syncQueue: SyncTransactionQueue,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val transitions: Transitions,
- private val animationTransitionHandler: EnterDesktopTaskTransitionHandler,
+ private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
+ private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
@ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
@@ -121,7 +122,7 @@
}
/** Move a task to desktop */
- fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
+ fun moveToDesktop(task: RunningTaskInfo) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
val wct = WindowContainerTransaction()
@@ -149,7 +150,7 @@
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- animationTransitionHandler.startTransition(
+ enterDesktopTaskTransitionHandler.startTransition(
Transitions.TRANSIT_ENTER_FREEFORM, wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
@@ -167,7 +168,8 @@
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- animationTransitionHandler.startTransition(Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
+ enterDesktopTaskTransitionHandler.startTransition(
+ Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -179,7 +181,7 @@
}
/** Move a task to fullscreen */
- fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
+ fun moveToFullscreen(task: RunningTaskInfo) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
@@ -191,8 +193,20 @@
}
}
+ fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
+ val wct = WindowContainerTransaction()
+ addMoveToFullscreenChanges(wct, task.token)
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ exitDesktopTaskTransitionHandler.startTransition(
+ Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
/** Move a task to the front **/
- fun moveTaskToFront(taskInfo: ActivityManager.RunningTaskInfo) {
+ fun moveTaskToFront(taskInfo: RunningTaskInfo) {
val wct = WindowContainerTransaction()
wct.reorder(taskInfo.token, true)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -259,7 +273,7 @@
request: TransitionRequestInfo
): WindowContainerTransaction? {
// Check if we should skip handling this transition
- val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+ val task: RunningTaskInfo? = request.triggerTask
val shouldHandleRequest =
when {
// Only handle open or to front transitions
@@ -368,16 +382,15 @@
taskSurface: SurfaceControl,
y: Float
) {
- val statusBarHeight = displayController
- .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ val statusBarHeight = getStatusBarHeight(taskInfo)
if (y <= statusBarHeight && visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer)
- visualIndicator?.createFullscreenIndicator()
+ visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
} else if (y > statusBarHeight && visualIndicator != null) {
- visualIndicator?.releaseFullscreenIndicator()
+ visualIndicator?.releaseVisualIndicator()
visualIndicator = null
}
}
@@ -393,16 +406,73 @@
taskInfo: RunningTaskInfo,
y: Float
) {
- val statusBarHeight = displayController
- .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ val statusBarHeight = getStatusBarHeight(taskInfo)
if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- moveToFullscreen(taskInfo.taskId)
- visualIndicator?.releaseFullscreenIndicator()
+ visualIndicator?.releaseVisualIndicator()
visualIndicator = null
+ moveToFullscreenWithAnimation(taskInfo)
}
}
/**
+ * Perform checks required on drag move. Create/release fullscreen indicator and transitions
+ * indicator to freeform or fullscreen dimensions as needed.
+ *
+ * @param taskInfo the task being dragged.
+ * @param taskSurface SurfaceControl of dragged task.
+ * @param y coordinate of dragged task. Used for checks against status bar height.
+ */
+ fun onDragPositioningMoveThroughStatusBar(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ y: Float
+ ) {
+ if (visualIndicator == null) {
+ visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+ displayController, context, taskSurface, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer)
+ visualIndicator?.createFullscreenIndicator()
+ }
+ val indicator = visualIndicator ?: return
+ if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
+ if (indicator.isFullscreen) {
+ indicator.transitionFullscreenIndicatorToFreeform()
+ }
+ } else if (!indicator.isFullscreen) {
+ indicator.transitionFreeformIndicatorToFullscreen()
+ }
+ }
+
+ /**
+ * Perform checks required when drag ends under status bar area.
+ *
+ * @param taskInfo the task being dragged.
+ * @param y height of drag, to be checked against status bar height.
+ */
+ fun onDragPositioningEndThroughStatusBar(
+ taskInfo: RunningTaskInfo,
+ freeformBounds: Rect
+ ) {
+ moveToDesktopWithAnimation(taskInfo, freeformBounds)
+ visualIndicator?.releaseVisualIndicator()
+ visualIndicator = null
+ }
+
+
+ private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
+ return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ }
+
+ /**
+ * Returns the threshold at which we transition a task into freeform when dragging a
+ * fullscreen task down from the status bar
+ */
+ private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
+ return 2 * getStatusBarHeight(taskInfo)
+ }
+
+
+ /**
* Adds a listener to find out about changes in the visibility of freeform tasks.
*
* @param listener the listener to add.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
new file mode 100644
index 0000000..d18e98a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+
+/**
+ * The {@link Transitions.TransitionHandler} that handles transitions for desktop mode tasks
+ * entering and exiting freeform.
+ */
+public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
+ private static final int FULLSCREEN_ANIMATION_DURATION = 336;
+ private final Context mContext;
+ private final Transitions mTransitions;
+ private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+
+ private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ public ExitDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Context context) {
+ this(transitions, SurfaceControl.Transaction::new, context);
+ }
+
+ private ExitDesktopTaskTransitionHandler(
+ Transitions transitions,
+ Supplier<SurfaceControl.Transaction> supplier,
+ Context context) {
+ mTransitions = transitions;
+ mTransactionSupplier = supplier;
+ mContext = context;
+ }
+
+ /**
+ * Starts Transition of a given type
+ * @param type Transition type
+ * @param wct WindowContainerTransaction for transition
+ */
+ public void startTransition(@WindowManager.TransitionType int type,
+ @NonNull WindowContainerTransaction wct) {
+ final IBinder token = mTransitions.startTransition(type, wct, this);
+ mPendingTransitionTokens.add(token);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean transitionHandled = false;
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
+ continue;
+ }
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
+ transitionHandled |= startChangeTransition(
+ transition, info.getType(), change, startT, finishCallback);
+ }
+ }
+
+ mPendingTransitionTokens.remove(transition);
+
+ return transitionHandled;
+ }
+
+ @VisibleForTesting
+ boolean startChangeTransition(
+ @NonNull IBinder transition,
+ @WindowManager.TransitionType int type,
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (!mPendingTransitionTokens.contains(transition)) {
+ return false;
+ }
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (type == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ // This Transition animates a task to fullscreen after being dragged to status bar
+ final Resources resources = mContext.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final int screenHeight = metrics.heightPixels;
+ final SurfaceControl sc = change.getLeash();
+ startT.setCrop(sc, null);
+ startT.apply();
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(FULLSCREEN_ANIMATION_DURATION);
+ final Rect startBounds = change.getStartAbsBounds();
+ final float scaleX = (float) startBounds.width() / screenWidth;
+ final float scaleY = (float) startBounds.height() / screenHeight;
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ Point startPos = new Point(startBounds.left,
+ startBounds.top);
+ animator.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ float currentScaleX = scaleX + ((1 - scaleX) * fraction);
+ float currentScaleY = scaleY + ((1 - scaleY) * fraction);
+ t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction));
+ t.setScale(sc, currentScaleX, currentScaleY);
+ t.apply();
+ });
+ animator.start();
+ return true;
+ }
+
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4c53f60..f70df83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -188,6 +188,11 @@
return mCurrentAnimator;
}
+ /** Reset animator state to prevent it from being used after its lifetime. */
+ public void resetAnimatorState() {
+ mCurrentAnimator = null;
+ }
+
private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f2f30ea..a939212 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -73,6 +73,7 @@
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -142,6 +143,23 @@
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
+ // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
+ private Runnable mPipFinishResizeWCTRunnable;
+
+ private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback =
+ new WindowContainerTransactionCallback() {
+ @Override
+ public void onTransactionReady(int id, SurfaceControl.Transaction t) {
+ t.apply();
+
+ // execute the runnable if non-null after WCT is applied to finish resizing pip
+ if (mPipFinishResizeWCTRunnable != null) {
+ mPipFinishResizeWCTRunnable.run();
+ mPipFinishResizeWCTRunnable = null;
+ }
+ }
+ };
+
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
new PipAnimationController.PipAnimationCallback() {
@@ -416,6 +434,26 @@
}
/**
+ * Override if the PiP should always use a fade-in animation during PiP entry.
+ *
+ * @return true if the mOneShotAnimationType should always be
+ * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+ */
+ protected boolean shouldAlwaysFadeIn() {
+ return false;
+ }
+
+ /**
+ * Whether the menu should get attached as early as possible when entering PiP.
+ *
+ * @return whether the menu should be attached before
+ * {@link PipBoundsAlgorithm#getEntryDestinationBounds()} is called.
+ */
+ protected boolean shouldAttachMenuEarly() {
+ return false;
+ }
+
+ /**
* Callback when Launcher starts swipe-pip-to-home operation.
* @return {@link Rect} for destination bounds.
*/
@@ -709,17 +747,26 @@
return;
}
+ if (shouldAlwaysFadeIn()) {
+ mOneShotAnimationType = ANIM_TYPE_ALPHA;
+ }
+
if (mWaitForFixedRotation) {
onTaskAppearedWithFixedRotation();
return;
}
+ if (shouldAttachMenuEarly()) {
+ mPipMenuController.attach(mLeash);
+ }
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- mPipMenuController.attach(mLeash);
+ if (!shouldAttachMenuEarly()) {
+ mPipMenuController.attach(mLeash);
+ }
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
info.pictureInPictureParams, currentBounds);
scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
@@ -834,7 +881,9 @@
@Nullable SurfaceControl.Transaction boundsChangeTransaction) {
// PiP menu is attached late in the process here to avoid any artifacts on the leash
// caused by addShellRoot when in gesture navigation mode.
- mPipMenuController.attach(mLeash);
+ if (!shouldAttachMenuEarly()) {
+ mPipMenuController.attach(mLeash);
+ }
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
@@ -1249,8 +1298,23 @@
/**
* Animates resizing of the pinned stack given the duration and start bounds.
* This is used when the starting bounds is not the current PiP bounds.
+ *
+ * @param pipFinishResizeWCTRunnable callback to run after window updates are complete
*/
public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
+ float startingAngle, Consumer<Rect> updateBoundsCallback,
+ Runnable pipFinishResizeWCTRunnable) {
+ mPipFinishResizeWCTRunnable = pipFinishResizeWCTRunnable;
+ if (mPipFinishResizeWCTRunnable != null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "mPipFinishResizeWCTRunnable is set to be called once window updates");
+ }
+
+ scheduleAnimateResizePip(fromBounds, toBounds, duration, startingAngle,
+ updateBoundsCallback);
+ }
+
+ private void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
float startingAngle, Consumer<Rect> updateBoundsCallback) {
if (mWaitForFixedRotation) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -1555,7 +1619,7 @@
mSplitScreenOptional.ifPresent(splitScreenController ->
splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
} else {
- mTaskOrganizer.applyTransaction(wct);
+ mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback);
}
}
@@ -1790,6 +1854,7 @@
animator::clearContentOverlay);
}
PipAnimationController.quietCancel(animator);
+ mPipAnimationController.resetAnimatorState();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 49a27c5..4a76a50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -272,6 +272,8 @@
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
if (requestHasPipEnter(request)) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: handle PiP enter request", TAG);
WindowContainerTransaction wct = new WindowContainerTransaction();
augmentRequest(transition, request, wct);
return wct;
@@ -731,6 +733,11 @@
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
+
+ if (mPipOrganizer.shouldAttachMenuEarly()) {
+ mPipMenuController.attach(leash);
+ }
+
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -745,7 +752,10 @@
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
- mTransitions.getMainExecutor().executeDelayed(() -> mPipMenuController.attach(leash), 0);
+ if (!mPipOrganizer.shouldAttachMenuEarly()) {
+ mTransitions.getMainExecutor().executeDelayed(
+ () -> mPipMenuController.attach(leash), 0);
+ }
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
@@ -785,6 +795,11 @@
tmpTransform.postRotate(rotationDelta);
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
+
+ if (mPipOrganizer.shouldAlwaysFadeIn()) {
+ mOneShotAnimationType = ANIM_TYPE_ALPHA;
+ }
+
if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
startTransaction.setAlpha(leash, 0f);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 1d12824..68952c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -588,8 +588,16 @@
final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+ // disable the pinch resizing until the final bounds are updated
+ final boolean prevEnablePinchResize = mEnablePinchResize;
+ mEnablePinchResize = false;
+
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback);
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
+ // reset the pinch resizing to its default state
+ mEnablePinchResize = prevEnablePinchResize;
+ });
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index d73723c..2f74fb6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -446,7 +446,7 @@
"%s: PiP has already been closed by custom close action", TAG);
return;
}
- removeTask(mPinnedTaskId);
+ mPipTaskOrganizer.removePip();
onPipDisappeared();
}
@@ -673,17 +673,6 @@
}
}
- private static void removeTask(int taskId) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: removeTask(), taskId=%d", TAG, taskId);
- try {
- ActivityTaskManager.getService().removeTask(taskId);
- } catch (Exception e) {
- ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Atm.removeTask() failed, %s", TAG, e);
- }
- }
-
private static String stateToName(@State int state) {
switch (state) {
case STATE_NO_PIP:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index f6856f1..0940490 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -85,4 +85,17 @@
mPipParamsChangedForwarder.notifySubtitleChanged(params.getSubtitle());
}
}
+
+ /**
+ * Override for TV since the menu bounds affect the PiP location. Additionally, we want to
+ * ensure that menu is shown immediately since it should always be visible on TV as it creates
+ * a border with rounded corners around the PiP.
+ */
+ protected boolean shouldAttachMenuEarly() {
+ return true;
+ }
+
+ protected boolean shouldAlwaysFadeIn() {
+ return true;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 8ebcf63..d3253a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -16,60 +16,43 @@
package com.android.wm.shell.pip.tv;
-import android.app.TaskInfo;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
+import android.content.Context;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipMenuController;
-import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
+
/**
* PiP Transition for TV.
- * TODO: Implement animation once TV is using Transitions.
*/
-public class TvPipTransition extends PipTransitionController {
- public TvPipTransition(
+public class TvPipTransition extends PipTransition {
+
+ public TvPipTransition(Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
- PipBoundsState pipBoundsState,
- PipMenuController pipMenuController,
+ TvPipBoundsState tvPipBoundsState,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipTransitionState pipTransitionState,
+ TvPipMenuController tvPipMenuController,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
- super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- tvPipBoundsAlgorithm, pipAnimationController);
+ PipAnimationController pipAnimationController,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreenController> splitScreenOptional) {
+ super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState,
+ pipDisplayLayoutState, pipTransitionState, tvPipMenuController,
+ tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ splitScreenOptional);
}
- @Override
- public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, int direction,
- SurfaceControl.Transaction tx) {
-
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @android.annotation.NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- return false;
- }
-
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- return null;
- }
}
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 5c64177..c8d6a5e8 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
@@ -456,7 +456,10 @@
cancel(mWillFinishToHome);
return;
}
- hasChangingApp = true;
+ // Don't consider order-only changes as changing apps.
+ if (!TransitionUtil.isOrderOnly(change)) {
+ hasChangingApp = true;
+ }
}
}
if (hasChangingApp && foundRecentsClosing) {
@@ -484,13 +487,14 @@
}
boolean didMergeThings = false;
if (closingTasks != null) {
- // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
+ // Potentially cancelling a task-switch. Move the tasks back to mPausing if they
+ // are in mOpening.
for (int i = 0; i < closingTasks.size(); ++i) {
final TransitionInfo.Change change = closingTasks.get(i);
int openingIdx = TaskState.indexOf(mOpeningTasks, change);
if (openingIdx < 0) {
- Slog.e(TAG, "Back to existing recents animation from an unrecognized "
- + "task: " + change.getTaskInfo().taskId);
+ Slog.w(TAG, "Closing a task that wasn't opening, this may be split or"
+ + " something unexpected: " + change.getTaskInfo().taskId);
continue;
}
mPausingTasks.add(mOpeningTasks.remove(openingIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d52abf7..5a92f78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -235,6 +235,7 @@
private TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
+ out.setDebugId(info.getDebugId());
if (withChanges) {
for (int i = 0; i < info.getChanges().size(); ++i) {
out.getChanges().add(info.getChanges().get(i));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 63c7969..3dd10a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -301,8 +301,8 @@
return true;
}
- // check if no-animation and skip animation if so.
- if (Transitions.isAllNoAnimation(info)) {
+ // Early check if the transition doesn't warrant an animation.
+ if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info)) {
startTransaction.apply();
finishTransaction.apply();
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 485b400..4e3d220 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -63,7 +63,7 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mTransition != transition) return false;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for %s.", mRemote, transition);
+ + " transition %s for #%d.", mRemote, info.getDebugId());
final IBinder.DeathRecipient remoteDied = () -> {
Log.e(Transitions.TAG, "Remote transition died, finishing");
@@ -113,9 +113,6 @@
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote"
- + " transition %s for %s.", mRemote, transition);
-
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
@@ -154,4 +151,10 @@
+ " for %s: %s", transition, remote);
return new WindowContainerTransaction();
}
+
+ @Override
+ public String toString() {
+ return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":"
+ + mRemote.getRemoteTransition();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 3c4e889..5b7231c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -101,8 +101,8 @@
}
RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
if (pendingRemote == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
- + "explicit remote, search filters for match for %s", transition, info);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition doesn't have "
+ + "explicit remote, search filters for match for %s", info);
// If no explicit remote, search filters until one matches
for (int i = mFilters.size() - 1; i >= 0; --i) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
@@ -116,8 +116,8 @@
}
}
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for %s to %s",
- transition, pendingRemote);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s",
+ info.getDebugId(), pendingRemote);
if (pendingRemote == null) return false;
@@ -184,9 +184,10 @@
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget).getRemoteTransition();
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s",
- transition, remote);
+ final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
+ final IRemoteTransition remote = remoteTransition.getRemoteTransition();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Merge into remote: %s",
+ remoteTransition);
if (remote == null) return;
IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index 0386ec3..1879bf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
-import android.util.Log;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -59,7 +58,6 @@
@Override
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishTransaction) {
- Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense");
mSleepTransitions.remove(transition);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4284993..681fa51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -140,6 +140,9 @@
/** Transition type to freeform in desktop mode. */
public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+ /** Transition type to fullscreen from desktop mode. */
+ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
@@ -182,6 +185,14 @@
/** Ordered list of transitions which have been merged into this one. */
private ArrayList<ActiveTransition> mMerged;
+
+ @Override
+ public String toString() {
+ if (mInfo != null && mInfo.getDebugId() >= 0) {
+ return "(#" + mInfo.getDebugId() + ")" + mToken;
+ }
+ return mToken.toString();
+ }
}
/** Keeps track of transitions which have been started, but aren't ready yet. */
@@ -516,6 +527,16 @@
return hasNoAnimation;
}
+ /**
+ * Check if all changes in this transition are only ordering changes. If so, we won't animate.
+ */
+ static boolean isAllOrderOnly(TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false;
+ }
+ return true;
+ }
+
@VisibleForTesting
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
@@ -529,8 +550,8 @@
activeTransition -> activeTransition.mToken).toArray()));
}
if (activeIdx > 0) {
- Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
- + " order: " + Arrays.toString(mPendingTransitions.stream().map(
+ Log.e(TAG, "Transition became ready out-of-order " + mPendingTransitions.get(activeIdx)
+ + ". Expected order: " + Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
// Move from pending to ready
@@ -547,6 +568,7 @@
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
// Sleep starts a process of forcing all prior transitions to finish immediately
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sleep");
finishForSleep(null /* forceFinish */);
return;
}
@@ -555,8 +577,8 @@
if (info.getRootCount() == 0 && !alwaysReportToKeyguard(info)) {
// No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
- transitionToken, info);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+ + " abort", active);
onAbort(active);
return;
}
@@ -585,6 +607,8 @@
&& allOccluded)) {
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Non-visible anim so abort: %s", active);
onAbort(active);
return;
}
@@ -652,21 +676,21 @@
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
- + " another transition %s is still animating. Notify the animating transition"
- + " in case they can be merged", ready.mToken, playing.mToken);
+ + " %s is still animating. Notify the animating transition"
+ + " in case they can be merged", ready, playing);
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
playing.mToken, (wct, cb) -> onMerged(playing, ready));
}
private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
- merged.mToken);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
+ merged, playing);
int readyIdx = 0;
if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
- Log.e(TAG, "Merged transition out-of-order?");
+ Log.e(TAG, "Merged transition out-of-order? " + merged);
readyIdx = mReadyTransitions.indexOf(merged);
if (readyIdx < 0) {
- Log.e(TAG, "Merged a transition that is no-longer queued?");
+ Log.e(TAG, "Merged a transition that is no-longer queued? " + merged);
return;
}
}
@@ -687,6 +711,7 @@
}
private void playTransition(@NonNull ActiveTransition active) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionStarting(active.mToken);
}
@@ -788,12 +813,12 @@
int activeIdx = mActiveTransitions.indexOf(active);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
- + " a handler didn't properly deal with a merge. " + active.mToken,
+ + " a handler didn't properly deal with a merge. " + active,
new RuntimeException());
return;
} else if (activeIdx != 0) {
// Relevant right now since we only allow 1 active transition at a time.
- Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
+ Log.e(TAG, "Finishing a transition out of order. " + active);
}
mActiveTransitions.remove(activeIdx);
@@ -801,7 +826,7 @@
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
- + "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
+ + "(aborted=%b), notifying core %s", active.mAborted, active);
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
// yet, though, since nullness is used later to disambiguate malformed transitions.
@@ -917,6 +942,8 @@
/** Start a new transition directly. */
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
+ + "type=%d wct=%s handler=%s", type, wct, handler);
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startNewTransition(type, wct);
@@ -948,8 +975,7 @@
return;
}
if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
- Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
- + forceFinish.mToken);
+ Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + forceFinish);
forceFinish.mAborted = true;
// Last notify of it being consumed. Note: mHandler should never be null,
// but check just to be safe.
@@ -967,6 +993,8 @@
// Try to signal that we are sleeping by attempting to merge the sleep transition
// into the playing one.
final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge SLEEP %s"
+ + " into %s", nextSleep, playing);
playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
playing.mToken, (wct, cb) -> {});
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 7595c96..ce10291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -31,6 +31,7 @@
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -90,6 +91,15 @@
&& !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
}
+ /** Returns `true` if `change` is only re-ordering. */
+ public static boolean isOrderOnly(TransitionInfo.Change change) {
+ return change.getMode() == TRANSIT_CHANGE
+ && (change.getFlags() & FLAG_MOVED_TO_TOP) != 0
+ && change.getStartAbsBounds().equals(change.getEndAbsBounds())
+ && (change.getLastParent() == null
+ || change.getLastParent().equals(change.getParent()));
+ }
+
/**
* Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you
* MUST call `test` in the same order that the changes appear in the TransitionInfo.
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 c0dcd0b..f998217 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
@@ -79,6 +79,7 @@
public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "DesktopModeWindowDecorViewModel";
+
private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
@@ -542,7 +543,7 @@
mTransitionDragActive = false;
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > statusBarHeight) {
+ if (ev.getY() > 2 * statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
@@ -567,9 +568,11 @@
return;
}
if (mTransitionDragActive) {
- final int statusBarHeight = mDisplayController
- .getDisplayLayout(
- relevantDecor.mTaskInfo.displayId).stableInsets().top;
+ mDesktopTasksController.ifPresent(
+ c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getY()));
+ final int statusBarHeight = getStatusBarHeight(
+ relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
if (!mDragToDesktopAnimationStarted) {
mDragToDesktopAnimationStarted = true;
@@ -644,7 +647,8 @@
@Override
public void onAnimationEnd(Animator animation) {
mDesktopTasksController.ifPresent(
- c -> c.moveToDesktopWithAnimation(relevantDecor.mTaskInfo,
+ c -> c.onDragPositioningEndThroughStatusBar(
+ relevantDecor.mTaskInfo,
calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575c..2cd1e1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -42,6 +42,9 @@
import com.android.internal.view.BaseIWindow;
+import java.util.Arrays;
+import java.util.List;
+
/**
* An input event listener registered to InputDispatcher to receive input events on task edges and
* and corners. Converts them to drag resize requests.
@@ -211,6 +214,10 @@
PRIVATE_FLAG_TRUSTED_OVERLAY,
0 /* inputFeatures */,
touchRegion);
+ List<Rect> cornersList = Arrays.asList(
+ mLeftTopCornerBounds, mLeftBottomCornerBounds,
+ mRightTopCornerBounds, mRightBottomCornerBounds);
+ mWindowSession.reportSystemGestureExclusionChanged(mFakeWindow, cornersList);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 91846fa..e986ee1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -218,36 +218,36 @@
assertLayers {
if (landscapePosLeft) {
splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
- .then()
- .isInvisible(component)
- .then()
- .splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
} else {
splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
- .then()
- .isInvisible(component)
- .then()
- .splitAppLayerBoundsSnapToDivider(
- component,
- landscapePosLeft,
- portraitPosTop,
- scenario.endRotation
- )
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
+ .then()
+ .isInvisible(component)
+ .then()
+ .splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index d0bca13..2474ecf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,8 +17,8 @@
package com.android.wm.shell.flicker.bubble
import android.os.SystemClock
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -26,8 +26,6 @@
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import org.junit.Assume
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -45,13 +43,8 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FlakyTest(bugId = 217777115)
open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
-
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
deleted file mode 100644
index 5e85eb8..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestShellTransit.kt
+++ /dev/null
@@ -1,39 +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.wm.shell.flicker.bubble
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FlakyTest(bugId = 217777115)
-class ChangeActiveActivityFromBubbleTestShellTransit(flicker: FlickerTest) :
- ChangeActiveActivityFromBubbleTest(flicker) {
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 1045a5a..93ee699 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -40,6 +40,7 @@
* Select "Auto-enter PiP" radio button
* Press Home button or swipe up to go Home and put [pipApp] in pip mode
* ```
+ *
* Notes:
* ```
* 1. All assertions are inherited from [EnterPipTest]
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 2d2588e..59918fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -38,6 +38,7 @@
* Launch an app in pip mode [pipApp],
* Swipe the pip window to the bottom-center of the screen and wait it disappear
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index 6c5a344..36c6f7c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -19,7 +19,6 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.LAUNCHER
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -43,25 +42,17 @@
@Presubmit
@Test
open fun pipWindowBecomesInvisible() {
- if (isShellTransitionsEnabled) {
- // When Shell transition is enabled, we change the windowing mode at start, but
- // update the visibility after the transition is finished, so we can't check isNotPinned
- // and isAppWindowInvisible in the same assertion block.
- flicker.assertWm {
- this.invoke("hasPipWindow") {
- it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
- }
- .then()
- .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
- }
- flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
- } else {
- flicker.assertWm {
- this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
- .then()
- .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
- }
+ // When Shell transition is enabled, we change the windowing mode at start, but
+ // update the visibility after the transition is finished, so we can't check isNotPinned
+ // and isAppWindowInvisible in the same assertion block.
+ flicker.assertWm {
+ this.invoke("hasPipWindow") {
+ it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
+ }
+ .then()
+ .invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
}
+ flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index e540ad5..d165832 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -38,6 +38,7 @@
* Click on the pip window
* Click on dismiss button and wait window disappear
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index e079d547..db18edb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -53,6 +53,7 @@
* Launch [pipApp] on a fixed landscape orientation
* Broadcast action [ACTION_ENTER_PIP] to enter pip mode
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
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 e40e5ea..51f0136 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
@@ -44,9 +44,7 @@
@Presubmit
@Test
open fun pipAppLayerAlwaysVisible() {
- flicker.assertLayers {
- this.isVisible(pipApp)
- }
+ flicker.assertLayers { this.isVisible(pipApp) }
}
/** Checks the content overlay appears then disappears during the animation */
@@ -55,11 +53,7 @@
open fun pipOverlayLayerAppearThenDisappear() {
val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
flicker.assertLayers {
- this.notContains(overlay)
- .then()
- .contains(overlay)
- .then()
- .notContains(overlay)
+ this.notContains(overlay).then().contains(overlay).then().notContains(overlay)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 1f060e9..f1925d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -35,6 +35,7 @@
* Launch an app in full screen
* Press an "enter pip" button to put [pipApp] in pip mode
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 313631c..3e0e37d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,16 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import org.junit.Assume
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -42,6 +37,7 @@
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -72,19 +68,4 @@
wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
- @Test
- override fun pipLayerExpands() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
-
- @Presubmit
- @Test
- fun pipLayerExpands_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 93ffdd8d..603f995 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -16,16 +16,11 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.filters.RequiresDevice
-import org.junit.Assume
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -41,6 +36,7 @@
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen via an intent
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -71,37 +67,4 @@
wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
-
- /** {@inheritDoc} */
- @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.statusBarLayerPositionAtStartAndEnd()
- }
-
- @Presubmit
- @Test
- fun statusBarLayerRotatesScales_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- super.statusBarLayerPositionAtStartAndEnd()
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
- @Test
- override fun pipLayerExpands() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
-
- @Presubmit
- @Test
- fun pipLayerExpands_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- super.pipLayerExpands()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 7d5f740..6deba1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -40,6 +40,7 @@
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
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 9c00744..d8d57d2 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
@@ -40,6 +40,7 @@
* Launch [testApp]
* Check if pip window moves down (visually)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index c23838a..a626713 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -19,7 +19,6 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -28,8 +27,6 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.Assume.assumeFalse
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,11 +41,6 @@
open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
- @Before
- open fun before() {
- assumeFalse(isShellTransitionsEnabled)
- }
-
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
deleted file mode 100644
index 6f81116..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestShellTransit.kt
+++ /dev/null
@@ -1,47 +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.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import androidx.test.filters.RequiresDevice
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestShellTransit(flicker: FlickerTest) :
- MovePipOnImeVisibilityChangeTest(flicker) {
-
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-
- @Presubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index c8d5624..ae3f879 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -40,6 +40,7 @@
* Press home
* Check if pip window moves up (visually)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 083cfd2..4e2a4e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -30,9 +30,7 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/**
- * Test the dragging of a PIP window.
- */
+/** Test the dragging of a PIP window. */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -59,9 +57,7 @@
pipApp.exit(wmHelper)
tapl.setEnableRotation(false)
}
- transitions {
- pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50)
- }
+ transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
}
@Postsubmit
@@ -92,4 +88,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 53ce393..9fe9f52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.pip
+import android.graphics.Rect
import android.platform.test.annotations.Postsubmit
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.graphics.Rect
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -33,14 +33,12 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/**
- * Test the snapping of a PIP window via dragging, releasing, and checking its final location.
- */
+/** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker){
+class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
// represents the direction in which the pip window should be snapping
private var willSnapRight: Boolean = true
@@ -60,8 +58,12 @@
// get the initial region bounds and cache them
val initRegion = pipApp.getWindowRect(wmHelper)
- startBounds
- .set(initRegion.left, initRegion.top, initRegion.right, initRegion.bottom)
+ startBounds.set(
+ initRegion.left,
+ initRegion.top,
+ initRegion.right,
+ initRegion.bottom
+ )
// drag the pip window away from the edge
pipApp.dragPipWindowAwayFromEdge(wmHelper, 50)
@@ -108,4 +110,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 2cf8f61..703784d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -43,6 +43,7 @@
* Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
* (usually, 0->90 and 90->0)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 17f174b..5180791 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -107,18 +107,15 @@
}
}
- @Presubmit
- @Test
- fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
@Presubmit
@Test
fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun primaryAppBoundsChanges() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitAppLayerBoundsChanges(
primaryApp,
landscapePosLeft = true,
@@ -135,11 +132,6 @@
portraitPosTop = true
)
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 5b06c9c..69da1e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -79,21 +79,24 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
- appExistAtStart = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index c840183..1773846 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -82,21 +82,19 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, sendNotificationApp,
- fromOtherApp = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
@@ -105,23 +103,6 @@
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.assertLayers {
- this.isInvisible(sendNotificationApp)
- .then()
- .isVisible(sendNotificationApp)
- .then()
- .isInvisible(sendNotificationApp)
- .then()
- .isVisible(sendNotificationApp)
- }
- }
-
- // TODO(b/245472831): Align to legacy transition after shell transition ready.
- @Presubmit
- @Test
- fun secondaryAppLayerBecomesVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.layerBecomesVisible(sendNotificationApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 5c99209..3bea66e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,11 +16,11 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -80,21 +80,24 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
- appExistAtStart = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
- @Presubmit
+ @FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@Presubmit
@Test
- fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
@@ -103,23 +106,6 @@
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.assertLayers {
- this.isInvisible(secondaryApp)
- .then()
- .isVisible(secondaryApp)
- .then()
- .isInvisible(secondaryApp)
- .then()
- .isVisible(secondaryApp)
- }
- }
-
- // TODO(b/245472831): Align to legacy transition after shell transition ready.
- @Presubmit
- @Test
- fun secondaryAppLayerBecomesVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.layerBecomesVisible(secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 2855c71..9f4cb8c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -20,7 +20,6 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -31,7 +30,6 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,19 +71,7 @@
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @FlakyTest
- @Test
- fun primaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
-
- @Presubmit
- @Test
- fun primaryAppLayerBecomesVisibleShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index c29a917..a33d8ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -20,7 +20,6 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -31,7 +30,6 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,19 +71,7 @@
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @FlakyTest
- @Test
- fun primaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
-
- @Presubmit
- @Test
- fun primaryAppLayerBecomesVisibleShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- flicker.layerBecomesVisible(primaryApp)
- }
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 4ccc467..c9bd695 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -81,7 +81,8 @@
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
- @Mock lateinit var transitionHandler: EnterDesktopTaskTransitionHandler
+ @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
+ @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
lateinit var mockitoSession: StaticMockitoSession
lateinit var controller: DesktopTasksController
@@ -117,7 +118,8 @@
syncQueue,
rootTaskDisplayAreaOrganizer,
transitions,
- transitionHandler,
+ enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler,
desktopModeTaskRepository,
TestShellExecutor()
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
new file mode 100644
index 0000000..2c5a5cd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+/** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
+@SmallTest
+public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
+
+ @Mock
+ private Transitions mTransitions;
+ @Mock
+ IBinder mToken;
+ @Mock
+ Supplier<SurfaceControl.Transaction> mTransactionFactory;
+ @Mock
+ Context mContext;
+ @Mock
+ DisplayMetrics mDisplayMetrics;
+ @Mock
+ Resources mResources;
+ @Mock
+ SurfaceControl.Transaction mStartT;
+ @Mock
+ SurfaceControl.Transaction mFinishT;
+ @Mock
+ SurfaceControl.Transaction mAnimationT;
+ @Mock
+ Transitions.TransitionFinishCallback mTransitionFinishCallback;
+ @Mock
+ ShellExecutor mExecutor;
+
+ private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mExecutor).when(mTransitions).getMainExecutor();
+ doReturn(mAnimationT).when(mTransactionFactory).get();
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+ when(mResources.getDisplayMetrics())
+ .thenReturn(getContext().getResources().getDisplayMetrics());
+
+ mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
+ mContext);
+ }
+
+ @Test
+ public void testTransitExitDesktopModeAnimation() throws Throwable {
+ final int transitionType = Transitions.TRANSIT_EXIT_DESKTOP_MODE;
+ final int taskId = 1;
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ doReturn(mToken).when(mTransitions)
+ .startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
+
+ mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+
+ TransitionInfo.Change change =
+ createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_EXIT_DESKTOP_MODE, change);
+ ArrayList<Exception> exceptions = new ArrayList<>();
+ runOnUiThread(() -> {
+ try {
+ assertTrue(mExitDesktopTaskTransitionHandler
+ .startAnimation(mToken, info, mStartT, mFinishT,
+ mTransitionFinishCallback));
+ } catch (Exception e) {
+ exceptions.add(e);
+ }
+ });
+ if (!exceptions.isEmpty()) {
+ throw exceptions.get(0);
+ }
+ }
+
+ private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ SurfaceControl.Builder b = new SurfaceControl.Builder()
+ .setName("test task");
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ taskInfo.token, b.build());
+ change.setMode(type);
+ change.setTaskInfo(taskInfo);
+ return change;
+ }
+
+ private static TransitionInfo createTransitionInfo(
+ @WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) {
+ TransitionInfo info = new TransitionInfo(type, 0);
+ info.addChange(change);
+ return info;
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 5b62a94..ada3455 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -159,7 +159,7 @@
mPipResizeGestureHandler.onPinchResize(upEvent);
verify(mPipTaskOrganizer, times(1))
- .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+ .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
assertTrue("The new size should be bigger than the original PiP size.",
mPipResizeGestureHandler.getLastResizeBounds().width()
@@ -198,7 +198,7 @@
mPipResizeGestureHandler.onPinchResize(upEvent);
verify(mPipTaskOrganizer, times(1))
- .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any());
+ .scheduleAnimateResizePip(any(), any(), anyInt(), anyFloat(), any(), any());
assertTrue("The new size should be smaller than the original PiP size.",
mPipResizeGestureHandler.getLastResizeBounds().width()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 1b29146..a9f311f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -181,7 +181,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null, null);
+ new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -407,7 +407,8 @@
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
+ new RemoteTransition(new TestRemoteTransition(), "Test"),
+ mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 60d6978..5cd548b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -277,7 +277,7 @@
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */,
- new RemoteTransition(testRemote)));
+ new RemoteTransition(testRemote, "Test")));
verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
@@ -422,7 +422,7 @@
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- transitions.registerRemote(filter, new RemoteTransition(testRemote));
+ transitions.registerRemote(filter, new RemoteTransition(testRemote, "Test"));
mMainExecutor.flushAll();
IBinder transitToken = new Binder();
@@ -466,11 +466,12 @@
final int transitType = TRANSIT_FIRST_CUSTOM + 1;
OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor,
- new RemoteTransition(testRemote));
+ new RemoteTransition(testRemote, "Test"));
// Verify that it responds to the remote but not other things.
IBinder transitToken = new Binder();
assertNotNull(oneShot.handleRequest(transitToken,
- new TransitionRequestInfo(transitType, null, new RemoteTransition(testRemote))));
+ new TransitionRequestInfo(transitType, null,
+ new RemoteTransition(testRemote, "Test"))));
assertNull(oneShot.handleRequest(transitToken,
new TransitionRequestInfo(transitType, null, null)));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 3b12972..5d79104 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -738,6 +738,9 @@
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
+ data: [
+ ":hwuimicro",
+ ],
}
// ------------------------
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 768dfcd..706f18c 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -85,28 +85,20 @@
}
static SkMatrix createMatrixFromBufferTransform(SkScalar width, SkScalar height, int transform) {
- auto matrix = SkMatrix();
switch (transform) {
case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
- matrix.setRotate(90);
- matrix.postTranslate(width, 0);
- break;
+ return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
- matrix.setRotate(180);
- matrix.postTranslate(width, height);
- break;
+ return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
- matrix.setRotate(270);
- matrix.postTranslate(0, width);
- break;
+ return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1);
default:
ALOGE("Invalid transform provided. Transform should be validated from"
"the java side. Leveraging identity transform as a fallback");
[[fallthrough]];
case ANATIVEWINDOW_TRANSFORM_IDENTITY:
- break;
+ return SkMatrix::I();
}
- return matrix;
}
static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, jlong renderProxy,
@@ -117,8 +109,8 @@
auto skHeight = static_cast<SkScalar>(height);
auto matrix = createMatrixFromBufferTransform(skWidth, skHeight, transform);
auto colorSpace = GraphicsJNI::getNativeColorSpace(colorspacePtr);
- proxy->setHardwareBufferRenderParams(
- HardwareBufferRenderParams(matrix, colorSpace, createRenderCallback(env, consumer)));
+ proxy->setHardwareBufferRenderParams(HardwareBufferRenderParams(
+ width, height, matrix, colorSpace, createRenderCallback(env, consumer)));
nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
UiFrameInfoBuilder(proxy->frameInfo())
.setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 202a62c..cc987bc 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -69,15 +69,9 @@
}
Frame SkiaOpenGLPipeline::getFrame() {
- if (mHardwareBuffer) {
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(mHardwareBuffer, &description);
- return Frame(description.width, description.height, 0);
- } else {
- LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
- "drawRenderNode called on a context with no surface!");
- return mEglManager.beginFrame(mEglSurface);
- }
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "drawRenderNode called on a context with no surface!");
+ return mEglManager.beginFrame(mEglSurface);
}
IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 99298bc..c8f2e69 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -66,15 +66,8 @@
}
Frame SkiaVulkanPipeline::getFrame() {
- if (mHardwareBuffer) {
- AHardwareBuffer_Desc description;
- AHardwareBuffer_describe(mHardwareBuffer, &description);
- return Frame(description.width, description.height, 0);
- } else {
- LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr,
- "getFrame() called on a context with no surface!");
- return vulkanManager().dequeueNextBuffer(mVkSurface);
- }
+ LOG_ALWAYS_FATAL_IF(mVkSurface == nullptr, "getFrame() called on a context with no surface!");
+ return vulkanManager().dequeueNextBuffer(mVkSurface);
}
IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index dd781bb..6b2c995 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -528,6 +528,14 @@
sendLoadResetHint();
}
+Frame CanvasContext::getFrame() {
+ if (mHardwareBuffer != nullptr) {
+ return {mBufferParams.getLogicalWidth(), mBufferParams.getLogicalHeight(), 0};
+ } else {
+ return mRenderPipeline->getFrame();
+ }
+}
+
void CanvasContext::draw() {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
@@ -569,7 +577,8 @@
mCurrentFrameInfo->markIssueDrawCommandsStart();
- Frame frame = mRenderPipeline->getFrame();
+ Frame frame = getFrame();
+
SkRect windowDirty = computeDirtyRect(frame, &dirty);
ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b26c018..3f25339 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -264,6 +264,8 @@
FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId);
+ Frame getFrame();
+
// The same type as Frame.mWidth and Frame.mHeight
int32_t mLastFrameWidth = 0;
int32_t mLastFrameHeight = 0;
diff --git a/libs/hwui/renderthread/HardwareBufferRenderParams.h b/libs/hwui/renderthread/HardwareBufferRenderParams.h
index 91fe3f6..8c942d0 100644
--- a/libs/hwui/renderthread/HardwareBufferRenderParams.h
+++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h
@@ -36,9 +36,12 @@
class HardwareBufferRenderParams {
public:
HardwareBufferRenderParams() = default;
- HardwareBufferRenderParams(const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
+ HardwareBufferRenderParams(int32_t logicalWidth, int32_t logicalHeight,
+ const SkMatrix& transform, const sk_sp<SkColorSpace>& colorSpace,
RenderCallback&& callback)
- : mTransform(transform)
+ : mLogicalWidth(logicalWidth)
+ , mLogicalHeight(logicalHeight)
+ , mTransform(transform)
, mColorSpace(colorSpace)
, mRenderCallback(std::move(callback)) {}
const SkMatrix& getTransform() const { return mTransform; }
@@ -50,7 +53,12 @@
}
}
+ int32_t getLogicalWidth() { return mLogicalWidth; }
+ int32_t getLogicalHeight() { return mLogicalHeight; }
+
private:
+ int32_t mLogicalWidth;
+ int32_t mLogicalHeight;
SkMatrix mTransform = SkMatrix::I();
sk_sp<SkColorSpace> mColorSpace = SkColorSpace::MakeSRGB();
RenderCallback mRenderCallback = nullptr;
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 3813e97..3f3ad75 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -135,8 +135,12 @@
public String toString() {
StringBuilder s = new StringBuilder();
s.append("GnssMeasurementRequest[");
- s.append("@");
- TimeUtils.formatDuration(mIntervalMillis, s);
+ if (mIntervalMillis == PASSIVE_INTERVAL) {
+ s.append("passive");
+ } else {
+ s.append("@");
+ TimeUtils.formatDuration(mIntervalMillis, s);
+ }
if (mFullTracking) {
s.append(", FullTracking");
}
diff --git a/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl b/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl
index dcf3945..47d8426 100644
--- a/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl
@@ -25,14 +25,14 @@
oneway interface IInjectGlobalEvent {
/**
- * Request a fake STHAL restart.
+ * Trigger a fake STHAL restart.
* This invalidates the {@link IInjectGlobalEvent}.
*/
void triggerRestart();
/**
- * Triggers global resource contention into the fake STHAL. Loads/startRecognition
- * will fail with RESOURCE_CONTENTION.
+ * Set global resource contention within the fake STHAL. Loads/startRecognition
+ * will fail with {@code RESOURCE_CONTENTION} until unset.
* @param isContended - true to enable resource contention. false to disable resource contention
* and resume normal functionality.
* @param callback - Call {@link IAcknowledgeEvent#eventReceived()} on this interface once
@@ -40,4 +40,11 @@
*/
void setResourceContention(boolean isContended, IAcknowledgeEvent callback);
+ /**
+ * Trigger an
+ * {@link android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback#onResourcesAvailable}
+ * callback from the fake STHAL. This callback is used to signal to the framework that
+ * previous operations which failed may now succeed.
+ */
+ void triggerOnResourcesAvailable();
}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 37050df..62c4a51 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -29,6 +29,7 @@
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
+import android.media.RoutingSessionInfo;
import android.media.VolumeProvider;
import android.media.VolumeProvider.ControlType;
import android.media.session.MediaSession.QueueItem;
@@ -1001,8 +1002,11 @@
* @param maxVolume The max volume. Should be equal or greater than zero.
* @param currentVolume The current volume. Should be in the interval [0, maxVolume].
* @param audioAttrs The audio attributes for this playback. Should not be null.
- * @param volumeControlId The volume control ID. This is used for matching
- * {@link RoutingSessionInfo} and {@link MediaSession}.
+ * @param volumeControlId The {@link RoutingSessionInfo#getId() routing session id} of the
+ * {@link RoutingSessionInfo} associated with this controller, or null if not
+ * applicable. This id allows mapping this controller to a routing session which, when
+ * applicable, provides information about the remote device, and support for volume
+ * adjustment.
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/native/android/tests/activitymanager/nativeTests/Android.bp b/native/android/tests/activitymanager/nativeTests/Android.bp
index 528ac12..ebd7533 100644
--- a/native/android/tests/activitymanager/nativeTests/Android.bp
+++ b/native/android/tests/activitymanager/nativeTests/Android.bp
@@ -45,4 +45,7 @@
required: [
"UidImportanceHelperApp",
],
+ data: [
+ ":UidImportanceHelperApp",
+ ],
}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index c898fe5..d87abb9 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
<string name="app_label">Companion Device Manager</string>
<!-- Title of the device association confirmation dialog. -->
- <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong></string>
+ <string name="confirmation_title">Allow <strong><xliff:g id="app_name" example="Android Wear">%1$s</xliff:g></strong> to access <strong><xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g></strong>?</string>
<!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
@@ -34,7 +34,7 @@
<string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions</string>
+ <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -48,7 +48,7 @@
<string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone</string>
+ <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -194,4 +194,10 @@
<!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
+ <!-- The type of the device for phone [CHAR LIMIT=30] -->
+ <string name="device_type" product="default">phone</string>
+
+ <!-- The type of the device for tablet [CHAR LIMIT=30] -->
+ <string name="device_type" product="tablet">tablet</string>
+
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index ae08823..4154029 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -551,7 +551,8 @@
summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
mConstraintList.setVisibility(View.GONE);
} else {
- summary = getHtmlFromResources(this, SUMMARIES.get(deviceProfile));
+ summary = getHtmlFromResources(
+ this, SUMMARIES.get(deviceProfile), getString(R.string.device_type));
mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
setupPermissionList();
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 28f9453..452455c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -110,6 +110,11 @@
ResultReceiver::class.java
)
+ val cancellationRequest = getCancelUiRequest(intent)
+ val cancelUiRequestState = cancellationRequest?.let {
+ CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
+ }
+
initialUiState = when (requestInfo.type) {
RequestInfo.TYPE_CREATE -> {
val defaultProviderId = userConfigRepo.getDefaultProviderId()
@@ -128,6 +133,7 @@
isPasskeyFirstUse
)!!,
getCredentialUiState = null,
+ cancelRequestState = cancelUiRequestState
)
}
RequestInfo.TYPE_GET -> {
@@ -142,6 +148,7 @@
if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
else ProviderActivityState.READY_TO_LAUNCH,
isAutoSelectFlow = autoSelectEntry != null,
+ cancelRequestState = cancelUiRequestState
)
}
else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
@@ -238,12 +245,12 @@
}
}
- /** Return the request token whose UI should be cancelled, or null otherwise. */
- fun getCancelUiRequestToken(intent: Intent): IBinder? {
+ /** Return the cancellation request if present. */
+ fun getCancelUiRequest(intent: Intent): CancelUiRequest? {
return intent.extras?.getParcelable(
CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
CancelUiRequest::class.java
- )?.token
+ )
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5d72424..2efe1be 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -30,11 +30,13 @@
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.common.Constants
import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.createflow.CreateCredentialScreen
import com.android.credentialmanager.createflow.hasContentToDisplay
import com.android.credentialmanager.getflow.GetCredentialScreen
@@ -49,10 +51,9 @@
super.onCreate(savedInstanceState)
Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
try {
- if (CredentialManagerRepo.getCancelUiRequestToken(intent) != null) {
- Log.d(
- Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
- this.finish()
+ val (isCancellationRequest, shouldShowCancellationUi, _) =
+ maybeCancelUIUponRequest(intent)
+ if (isCancellationRequest && !shouldShowCancellationUi) {
return
}
val userConfigRepo = UserConfigRepo(this)
@@ -75,14 +76,15 @@
setIntent(intent)
Log.d(Constants.LOG_TAG, "Existing activity received new intent")
try {
- val cancelUiRequestToken = CredentialManagerRepo.getCancelUiRequestToken(intent)
val viewModel: CredentialSelectorViewModel by viewModels()
- if (cancelUiRequestToken != null &&
- viewModel.shouldCancelCurrentUi(cancelUiRequestToken)) {
- Log.d(
- Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
- this.finish()
- return
+ val (isCancellationRequest, shouldShowCancellationUi, appDisplayName) =
+ maybeCancelUIUponRequest(intent, viewModel)
+ if (isCancellationRequest) {
+ if (shouldShowCancellationUi) {
+ viewModel.onCancellationUiRequested(appDisplayName)
+ } else {
+ return
+ }
} else {
val userConfigRepo = UserConfigRepo(this)
val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
@@ -93,11 +95,41 @@
}
}
+ /**
+ * Cancels the UI activity if requested by the backend. Different from the other finishing
+ * helpers, this does not report anything back to the Credential Manager service backend.
+ *
+ * Can potentially show a transient snackbar before finishing, if the request specifies so.
+ *
+ * Returns <isCancellationRequest, shouldShowCancellationUi, appDisplayName>.
+ */
+ private fun maybeCancelUIUponRequest(
+ intent: Intent,
+ viewModel: CredentialSelectorViewModel? = null
+ ): Triple<Boolean, Boolean, String?> {
+ val cancelUiRequest = CredentialManagerRepo.getCancelUiRequest(intent)
+ ?: return Triple(false, false, null)
+ if (viewModel != null && !viewModel.shouldCancelCurrentUi(cancelUiRequest.token)) {
+ // Cancellation was for a different request, don't cancel the current UI.
+ return Triple(false, false, null)
+ }
+ val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationUi()
+ Log.d(
+ Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
+ " ui = $shouldShowCancellationUi")
+ val appDisplayName = getAppLabel(packageManager, cancelUiRequest.appPackageName)
+ if (!shouldShowCancellationUi) {
+ this.finish()
+ }
+ return Triple(true, shouldShowCancellationUi, appDisplayName)
+ }
+
+
@ExperimentalMaterialApi
@Composable
- fun CredentialManagerBottomSheet(
+ private fun CredentialManagerBottomSheet(
credManRepo: CredentialManagerRepo,
- userConfigRepo: UserConfigRepo
+ userConfigRepo: UserConfigRepo,
) {
val viewModel: CredentialSelectorViewModel = viewModel {
CredentialSelectorViewModel(credManRepo, userConfigRepo)
@@ -113,7 +145,17 @@
val createCredentialUiState = viewModel.uiState.createCredentialUiState
val getCredentialUiState = viewModel.uiState.getCredentialUiState
- if (createCredentialUiState != null && hasContentToDisplay(createCredentialUiState)) {
+ val cancelRequestState = viewModel.uiState.cancelRequestState
+ if (cancelRequestState != null) {
+ if (cancelRequestState.appDisplayName == null) {
+ Log.d(Constants.LOG_TAG, "Received UI cancel request with an invalid package name.")
+ this.finish()
+ return
+ } else {
+ UiCancellationScreen(cancelRequestState.appDisplayName)
+ }
+ } else if (
+ createCredentialUiState != null && hasContentToDisplay(createCredentialUiState)) {
CreateCredentialScreen(
viewModel = viewModel,
createCredentialUiState = createCredentialUiState,
@@ -122,15 +164,15 @@
} else if (getCredentialUiState != null && hasContentToDisplay(getCredentialUiState)) {
if (isFallbackScreen(getCredentialUiState)) {
GetGenericCredentialScreen(
- viewModel = viewModel,
- getCredentialUiState = getCredentialUiState,
- providerActivityLauncher = launcher
+ viewModel = viewModel,
+ getCredentialUiState = getCredentialUiState,
+ providerActivityLauncher = launcher
)
} else {
GetCredentialScreen(
- viewModel = viewModel,
- getCredentialUiState = getCredentialUiState,
- providerActivityLauncher = launcher
+ viewModel = viewModel,
+ getCredentialUiState = getCredentialUiState,
+ providerActivityLauncher = launcher
)
}
} else {
@@ -172,4 +214,13 @@
)
this.finish()
}
+
+ @Composable
+ private fun UiCancellationScreen(appDisplayName: String) {
+ Snackbar(
+ contentText = stringResource(R.string.request_cancelled_by, appDisplayName),
+ onDismiss = { this@CredentialSelectorActivity.finish() },
+ dismissOnTimeout = true,
+ )
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index e49e3f1..7eb3bf4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -51,6 +51,11 @@
// True if the UI has one and only one auto selectable entry. Its provider activity will be
// launched immediately, and canceling it will cancel the whole UI flow.
val isAutoSelectFlow: Boolean = false,
+ val cancelRequestState: CancelUiRequestState?,
+)
+
+data class CancelUiRequestState(
+ val appDisplayName: String?,
)
class CredentialSelectorViewModel(
@@ -76,6 +81,10 @@
uiState = uiState.copy(dialogState = DialogState.COMPLETE)
}
+ fun onCancellationUiRequested(appDisplayName: String?) {
+ uiState = uiState.copy(cancelRequestState = CancelUiRequestState(appDisplayName))
+ }
+
/** Close the activity and don't report anything to the backend.
* Example use case is the no-auth-info snackbar where the activity should simply display the
* UI and then be dismissed. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 783cf3b..43da980 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -64,7 +64,7 @@
import org.json.JSONObject
// TODO: remove all !! checks
-private fun getAppLabel(
+fun getAppLabel(
pm: PackageManager,
appPackageName: String
): String? {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
index 514ff90..dfff3d6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
@@ -30,20 +30,24 @@
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalAccessibilityManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.credentialmanager.R
import com.android.credentialmanager.common.material.Scrim
import com.android.credentialmanager.ui.theme.Shapes
+import kotlinx.coroutines.delay
@Composable
fun Snackbar(
contentText: String,
action: (@Composable () -> Unit)? = null,
onDismiss: () -> Unit,
+ dismissOnTimeout: Boolean = false,
) {
BoxWithConstraints {
Box(Modifier.fillMaxSize()) {
@@ -89,4 +93,20 @@
}
}
}
+ val accessibilityManager = LocalAccessibilityManager.current
+ LaunchedEffect(true) {
+ if (dismissOnTimeout) {
+ // Same as SnackbarDuration.Short
+ val originalDuration = 4000L
+ val duration = if (accessibilityManager == null) originalDuration else
+ accessibilityManager.calculateRecommendedTimeoutMillis(
+ originalDuration,
+ containsIcons = true,
+ containsText = true,
+ containsControls = action != null,
+ )
+ delay(duration)
+ onDismiss()
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index a3d632c..e884cf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -305,10 +305,11 @@
synchronized (mProfileLock) {
if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
for (CachedBluetoothDevice member : getMemberDevice()) {
- Log.d(TAG, "Disconnect the member(" + member.getAddress() + ")");
+ Log.d(TAG, "Disconnect the member:" + member);
member.disconnect();
}
}
+ Log.d(TAG, "Disconnect " + this);
mDevice.disconnect();
}
// Disconnect PBAP server in case its connected
@@ -440,11 +441,11 @@
Log.d(TAG, "No profiles. Maybe we will connect later for device " + mDevice);
return;
}
-
+ Log.d(TAG, "connect " + this);
mDevice.connect();
if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
for (CachedBluetoothDevice member : getMemberDevice()) {
- Log.d(TAG, "connect the member(" + member.getAddress() + ")");
+ Log.d(TAG, "connect the member:" + member);
member.connect();
}
}
@@ -530,7 +531,7 @@
}
// TODO: do any of these need to run async on a background thread?
- private void fillData() {
+ void fillData() {
updateProfiles();
fetchActiveDevices();
migratePhonebookPermissionChoice();
@@ -933,14 +934,15 @@
@Override
public String toString() {
- return "CachedBluetoothDevice ("
+ return "CachedBluetoothDevice{"
+ "anonymizedAddress="
+ mDevice.getAnonymizedAddress()
+ ", name="
+ getName()
+ ", groupId="
+ mGroupId
- + ")";
+ + ", member=" + mMemberDevices
+ + "}";
}
@Override
@@ -1482,6 +1484,7 @@
* Store the member devices that are in the same coordinated set.
*/
public void addMemberDevice(CachedBluetoothDevice memberDevice) {
+ Log.d(TAG, this + " addMemberDevice = " + memberDevice);
mMemberDevices.add(memberDevice);
}
@@ -1511,13 +1514,14 @@
mDevice = newMainDevice.mDevice;
mRssi = newMainDevice.mRssi;
mJustDiscovered = newMainDevice.mJustDiscovered;
+ fillData();
// Set sub device from backup
newMainDevice.release();
newMainDevice.mDevice = tmpDevice;
newMainDevice.mRssi = tmpRssi;
newMainDevice.mJustDiscovered = tmpJustDiscovered;
- fetchActiveDevices();
+ newMainDevice.fillData();
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 20a6cd8..356bb82 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -71,7 +71,7 @@
return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
- for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
+ for (Map.Entry<Integer, ParcelUuid> entry : groupIdMap.entrySet()) {
if (entry.getValue().equals(BluetoothUuid.CAP)) {
return entry.getKey();
}
@@ -153,72 +153,13 @@
return;
}
log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString());
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
- final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
- final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT()) ?
- leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
+ List<CachedBluetoothDevice> memberDevicesList = getMemberDevicesList(groupId);
CachedBluetoothDevice newMainDevice =
- mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
- if (newMainDevice != null) {
- final CachedBluetoothDevice finalNewMainDevice = newMainDevice;
- final List<CachedBluetoothDevice> memberDevices = mCachedDevices.stream()
- .filter(cachedDevice -> !cachedDevice.equals(finalNewMainDevice)
- && cachedDevice.getGroupId() == groupId)
- .collect(Collectors.toList());
- if (memberDevices == null || memberDevices.isEmpty()) {
- log("onGroupIdChanged: There is no member device in list.");
- return;
- }
- log("onGroupIdChanged: removed from UI device =" + memberDevices
- + ", with groupId=" + groupId + " mainDevice= " + newMainDevice);
- for (CachedBluetoothDevice memberDeviceItem : memberDevices) {
- Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
- if (!memberSet.isEmpty()) {
- log("onGroupIdChanged: Transfer the member list into new main device.");
- for (CachedBluetoothDevice memberListItem : memberSet) {
- if (!memberListItem.equals(newMainDevice)) {
- newMainDevice.addMemberDevice(memberListItem);
- }
- }
- memberSet.clear();
- }
+ getPreferredMainDeviceWithoutConectionState(groupId, memberDevicesList);
- newMainDevice.addMemberDevice(memberDeviceItem);
- mCachedDevices.remove(memberDeviceItem);
- mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
- }
-
- if (!mCachedDevices.contains(newMainDevice)) {
- mCachedDevices.add(newMainDevice);
- mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
- }
- } else {
- log("onGroupIdChanged: There is no main device from the LE profile.");
- int firstMatchedIndex = -1;
-
- for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
- final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
- if (cachedDevice.getGroupId() != groupId) {
- continue;
- }
-
- if (firstMatchedIndex == -1) {
- // Found the first one
- firstMatchedIndex = i;
- newMainDevice = cachedDevice;
- continue;
- }
-
- log("onGroupIdChanged: removed from UI device =" + cachedDevice
- + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
-
- newMainDevice.addMemberDevice(cachedDevice);
- mCachedDevices.remove(i);
- mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
- break;
- }
- }
+ log("onGroupIdChanged: The mainDevice= " + newMainDevice
+ + " and the memberDevicesList of groupId= " + groupId + " =" + memberDevicesList);
+ addMemberDevicesIntoMainDevice(memberDevicesList, newMainDevice);
}
// @return {@code true}, the event is processed inside the method. It is for updating
@@ -263,7 +204,7 @@
break;
}
- for (CachedBluetoothDevice device: memberSet) {
+ for (CachedBluetoothDevice device : memberSet) {
if (device.isConnected()) {
log("set device: " + device + " as the main device");
// Main device is disconnected and sub device is connected
@@ -296,7 +237,7 @@
continue;
}
- for (CachedBluetoothDevice memberDevice: memberSet) {
+ for (CachedBluetoothDevice memberDevice : memberSet) {
if (memberDevice != null && memberDevice.equals(device)) {
return cachedDevice;
}
@@ -310,7 +251,6 @@
* Check if the {@code groupId} is existed.
*
* @param groupId The group id
- *
* @return {@code true}, if we could find a device with this {@code groupId}; Otherwise,
* return {@code false}.
*/
@@ -322,6 +262,116 @@
return false;
}
+ private List<CachedBluetoothDevice> getMemberDevicesList(int groupId) {
+ return mCachedDevices.stream()
+ .filter(cacheDevice -> cacheDevice.getGroupId() == groupId)
+ .collect(Collectors.toList());
+ }
+
+ private CachedBluetoothDevice getPreferredMainDeviceWithoutConectionState(int groupId,
+ List<CachedBluetoothDevice> memberDevicesList) {
+ // First, priority connected lead device from LE profile
+ // Second, the DUAL mode device which has A2DP/HFP and LE audio
+ // Last, any one of LE device in the list.
+ if (memberDevicesList == null || memberDevicesList.isEmpty()) {
+ return null;
+ }
+
+ final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
+ final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+ final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT())
+ ? leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
+
+ if (mainBluetoothDevice != null) {
+ log("getPreferredMainDevice: The LeadDevice from LE profile is "
+ + mainBluetoothDevice.getAnonymizedAddress());
+ }
+
+ // 1st
+ CachedBluetoothDevice newMainDevice =
+ mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
+ if (newMainDevice != null) {
+ if (newMainDevice.isConnected()) {
+ log("getPreferredMainDevice: The connected LeadDevice from LE profile");
+ return newMainDevice;
+ } else {
+ log("getPreferredMainDevice: The LeadDevice is not connect.");
+ }
+ } else {
+ log("getPreferredMainDevice: The LeadDevice is not in the all of devices list");
+ }
+
+ // 2nd
+ newMainDevice = memberDevicesList.stream()
+ .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+ .anyMatch(profile -> profile instanceof A2dpProfile
+ || profile instanceof HeadsetProfile))
+ .findFirst().orElse(null);
+ if (newMainDevice != null) {
+ log("getPreferredMainDevice: The DUAL mode device");
+ return newMainDevice;
+ }
+
+ // last
+ if (!memberDevicesList.isEmpty()) {
+ newMainDevice = memberDevicesList.get(0);
+ }
+ return newMainDevice;
+ }
+
+ private void addMemberDevicesIntoMainDevice(List<CachedBluetoothDevice> memberDevicesList,
+ CachedBluetoothDevice newMainDevice) {
+ if (newMainDevice == null) {
+ log("addMemberDevicesIntoMainDevice: No main device. Do nothing.");
+ return;
+ }
+ if (memberDevicesList.isEmpty()) {
+ log("addMemberDevicesIntoMainDevice: No member device in list. Do nothing.");
+ return;
+ }
+ CachedBluetoothDevice mainDeviceOfNewMainDevice = findMainDevice(newMainDevice);
+ boolean isMemberInOtherMainDevice = mainDeviceOfNewMainDevice != null;
+ if (!memberDevicesList.contains(newMainDevice) && isMemberInOtherMainDevice) {
+ log("addMemberDevicesIntoMainDevice: The 'new main device' is not in list, and it is "
+ + "the member at other device. Do switch main and member.");
+ // To switch content and dispatch to notify UI change
+ mBtManager.getEventManager().dispatchDeviceRemoved(mainDeviceOfNewMainDevice);
+ mainDeviceOfNewMainDevice.switchMemberDeviceContent(newMainDevice);
+ mainDeviceOfNewMainDevice.refresh();
+ // It is necessary to do remove and add for updating the mapping on
+ // preference and device
+ mBtManager.getEventManager().dispatchDeviceAdded(mainDeviceOfNewMainDevice);
+ } else {
+ log("addMemberDevicesIntoMainDevice: Set new main device");
+ for (CachedBluetoothDevice memberDeviceItem : memberDevicesList) {
+ if (memberDeviceItem.equals(newMainDevice)) {
+ continue;
+ }
+ Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
+ if (!memberSet.isEmpty()) {
+ for (CachedBluetoothDevice memberSetItem : memberSet) {
+ if (!memberSetItem.equals(newMainDevice)) {
+ newMainDevice.addMemberDevice(memberSetItem);
+ }
+ }
+ memberSet.clear();
+ }
+
+ newMainDevice.addMemberDevice(memberDeviceItem);
+ mCachedDevices.remove(memberDeviceItem);
+ mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
+ }
+
+ if (!mCachedDevices.contains(newMainDevice)) {
+ mCachedDevices.add(newMainDevice);
+ mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
+ }
+ }
+ log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
+ + mCachedDevices);
+ }
+
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 1c179f8..6444f3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1150,9 +1150,11 @@
assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
+ verify(mCachedDevice).fillData();
assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
+ verify(mSubCachedDevice).fillData();
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 80030f7..02ec486 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -20,14 +20,19 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
+import android.providers.settings.BackingStoreProto;
+import android.providers.settings.CacheEntryProto;
+import android.providers.settings.GenerationRegistryProto;
import android.util.ArrayMap;
import android.util.MemoryIntArray;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
+import java.io.PrintWriter;
/**
* This class tracks changes for config/global/secure/system tables
@@ -292,4 +297,94 @@
int getMaxNumBackingStores() {
return mMaxNumBackingStore;
}
+
+ public void dumpProto(ProtoOutputStream proto) {
+ synchronized (mLock) {
+ final int numBackingStores = mKeyToBackingStoreMap.size();
+ proto.write(GenerationRegistryProto.NUM_BACKING_STORES, numBackingStores);
+ proto.write(GenerationRegistryProto.NUM_MAX_BACKING_STORES, getMaxNumBackingStores());
+
+ for (int i = 0; i < numBackingStores; i++) {
+ final long token = proto.start(GenerationRegistryProto.BACKING_STORES);
+ final int key = mKeyToBackingStoreMap.keyAt(i);
+ proto.write(BackingStoreProto.KEY, key);
+ try {
+ proto.write(BackingStoreProto.BACKING_STORE_SIZE,
+ mKeyToBackingStoreMap.valueAt(i).size());
+ } catch (IOException ignore) {
+ }
+ proto.write(BackingStoreProto.NUM_CACHED_ENTRIES,
+ mKeyToIndexMapMap.get(key).size());
+ final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
+ final MemoryIntArray backingStore = getBackingStoreLocked(key,
+ /* createIfNotExist= */ false);
+ if (indexMap == null || backingStore == null) {
+ continue;
+ }
+ for (String setting : indexMap.keySet()) {
+ try {
+ final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
+ backingStore, /* createIfNotExist= */ false);
+ if (index < 0) {
+ continue;
+ }
+ final long cacheEntryToken = proto.start(
+ BackingStoreProto.CACHE_ENTRIES);
+ final int generation = backingStore.get(index);
+ proto.write(CacheEntryProto.NAME,
+ setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
+ ? "UNSET" : setting);
+ proto.write(CacheEntryProto.GENERATION, generation);
+ proto.end(cacheEntryToken);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ proto.end(token);
+ }
+
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("GENERATION REGISTRY");
+ pw.println("Maximum number of backing stores:" + getMaxNumBackingStores());
+ synchronized (mLock) {
+ final int numBackingStores = mKeyToBackingStoreMap.size();
+ pw.println("Number of backing stores:" + numBackingStores);
+ for (int i = 0; i < numBackingStores; i++) {
+ final int key = mKeyToBackingStoreMap.keyAt(i);
+ pw.print("_Backing store for type:"); pw.print(SettingsState.settingTypeToString(
+ SettingsState.getTypeFromKey(key)));
+ pw.print(" user:"); pw.print(SettingsState.getUserIdFromKey(key));
+ try {
+ pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size());
+ } catch (IOException ignore) {
+ }
+ pw.println(" cachedEntries:" + mKeyToIndexMapMap.get(key).size());
+ final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
+ final MemoryIntArray backingStore = getBackingStoreLocked(key,
+ /* createIfNotExist= */ false);
+ if (indexMap == null || backingStore == null) {
+ continue;
+ }
+ for (String setting : indexMap.keySet()) {
+ try {
+ final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
+ backingStore, /* createIfNotExist= */ false);
+ if (index < 0) {
+ continue;
+ }
+ final int generation = backingStore.get(index);
+ pw.print(" setting: "); pw.print(
+ setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
+ ? "UNSET" : setting);
+ pw.println(" generation:" + generation);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/OWNERS b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
new file mode 100644
index 0000000..0b71816
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/OWNERS
@@ -0,0 +1 @@
+per-file WritableNamespacePrefixes.java = cbrubaker@google.com,tedbauer@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d49627e..d3a9e91 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -120,6 +120,17 @@
dumpProtoUserSettingsLocked(proto, SettingsServiceDumpProto.USER_SETTINGS,
settingsRegistry, UserHandle.of(users.keyAt(i)));
}
+
+ // Generation registry
+ dumpProtoGenerationRegistryLocked(proto, SettingsServiceDumpProto.GENERATION_REGISTRY,
+ settingsRegistry);
+ }
+
+ private static void dumpProtoGenerationRegistryLocked(@NonNull ProtoOutputStream proto,
+ long fieldId, SettingsProvider.SettingsRegistry settingsRegistry) {
+ final long token = proto.start(fieldId);
+ settingsRegistry.getGenerationRegistry().dumpProto(proto);
+ proto.end(token);
}
/**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5a8c594..7a97b78 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -900,6 +900,7 @@
} finally {
Binder.restoreCallingIdentity(identity);
}
+ mSettingsRegistry.mGenerationRegistry.dump(pw);
}
}
@@ -2323,7 +2324,15 @@
return;
} else if (hasAllowlistPermission) {
for (String flag : flags) {
- if (!DeviceConfig.getAdbWritableFlags().contains(flag)) {
+ boolean namespaceAllowed = false;
+ for (String allowlistedPrefix : WritableNamespacePrefixes.ALLOWLIST) {
+ if (flag.startsWith(allowlistedPrefix)) {
+ namespaceAllowed = true;
+ break;
+ }
+ }
+
+ if (!namespaceAllowed && !DeviceConfig.getAdbWritableFlags().contains(flag)) {
throw new SecurityException("Permission denial for flag '"
+ flag
+ "'; allowlist permission granted, but must add flag to the allowlist.");
@@ -6016,5 +6025,10 @@
return !a11yButtonTargetsSettings.isNull()
&& !TextUtils.isEmpty(a11yButtonTargetsSettings.getValue());
}
+
+ @NonNull
+ public GenerationRegistry getGenerationRegistry() {
+ return mGenerationRegistry;
+ }
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
new file mode 100644
index 0000000..28f25e0
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 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.providers.settings;
+
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Contains the list of prefixes for namespaces in which any flag can be written with adb.
+ * <p>
+ * A security review is required for any prefix that's added to this list. To add to
+ * the list, create a change and tag the OWNER. In the change description, include a
+ * description of the flag's functionality, and a justification for why it needs to be
+ * allowlisted.
+ */
+final class WritableNamespacePrefixes {
+ public static final Set<String> ALLOWLIST =
+ new ArraySet<String>(Arrays.asList(
+ "app_compat_overrides",
+ "game_overlay",
+ "namespace1"
+ ));
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ac75cc8..3007d4a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -182,7 +182,6 @@
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
- "androidx.test.ext.junit",
"com.google.android.material_material",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
@@ -191,6 +190,7 @@
"SystemUI-proto",
"monet",
"dagger2",
+ "jsr305",
"jsr330",
"lottie",
"LowLightDreamLib",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
index 91cb4ba..462c90b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/footerlayout_switch_page.xml
@@ -56,6 +56,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="16dp"
+ android:paddingEnd="16dp"
android:gravity="center_vertical"
android:textColor="@color/colorControlNormal"
android:textSize="@dimen/label_text_size"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 4b6f9a4..02d279f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -24,7 +24,9 @@
import android.os.Bundle;
import android.provider.Browser;
import android.provider.Settings;
+import android.view.View;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
@@ -56,6 +58,13 @@
initializeHelpAndFeedbackPreference();
}
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ view.setLayoutDirection(
+ view.getResources().getConfiguration().getLayoutDirection());
+ }
+
/**
* Returns large buttons settings state.
*
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index a25790a..5b7bbe8 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -194,13 +194,15 @@
/** Updates a11y menu layout position by configuring layout params. */
private void updateLayoutPosition() {
final Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
- final int orientation = mService.getResources().getConfiguration().orientation;
+ final Configuration configuration = mService.getResources().getConfiguration();
+ final int orientation = configuration.orientation;
if (display != null && orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ final boolean ltr = configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
switch (display.getRotation()) {
- case Surface.ROTATION_90:
+ case Surface.ROTATION_0:
case Surface.ROTATION_180:
mLayoutParameter.gravity =
- Gravity.END | Gravity.BOTTOM
+ (ltr ? Gravity.END : Gravity.START) | Gravity.BOTTOM
| Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
mLayoutParameter.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParameter.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -208,10 +210,10 @@
mLayoutParameter.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
mLayout.setBackgroundResource(R.drawable.shadow_90deg);
break;
- case Surface.ROTATION_0:
+ case Surface.ROTATION_90:
case Surface.ROTATION_270:
mLayoutParameter.gravity =
- Gravity.START | Gravity.BOTTOM
+ (ltr ? Gravity.START : Gravity.END) | Gravity.BOTTOM
| Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
mLayoutParameter.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParameter.height = WindowManager.LayoutParams.MATCH_PARENT;
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 03e1e66..197b217 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -384,8 +384,15 @@
}
@JvmStatic
- fun adaptRemoteAnimation(adapter: RemoteAnimationAdapter): RemoteTransition {
- return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication)
+ fun adaptRemoteAnimation(
+ adapter: RemoteAnimationAdapter,
+ debugName: String
+ ): RemoteTransition {
+ return RemoteTransition(
+ adaptRemoteRunner(adapter.runner),
+ adapter.callingApplication,
+ debugName
+ )
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index e73afe7..a7e95b5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.Assert
+import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -41,6 +42,18 @@
private const val DEBUG = true
private val KEY_TIMESTAMP = "appliedTimestamp"
+private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
+ key: TKey,
+ value: TVal,
+ onNew: () -> Unit
+): TVal {
+ val result = this.putIfAbsent(key, value)
+ if (result == null) {
+ onNew()
+ }
+ return result ?: value
+}
+
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -64,7 +77,7 @@
fun onAvailableClocksChanged() {}
}
- private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+ private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver =
object : ContentObserver(null) {
@@ -92,18 +105,12 @@
var isClockListChanged = false
for (clock in plugin.getClocks()) {
val id = clock.clockId
- var isNew = false
val info =
- availableClocks.getOrPut(id) {
- isNew = true
- ClockInfo(clock, plugin, manager)
+ availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
+ isClockListChanged = true
+ onConnected(id)
}
- if (isNew) {
- isClockListChanged = true
- onConnected(id)
- }
-
if (manager != info.manager) {
Log.e(
TAG,
@@ -254,10 +261,8 @@
return
}
- android.util.Log.e("HAWK", "triggerOnCurrentClockChanged")
scope.launch(mainDispatcher) {
assertMainThread()
- android.util.Log.e("HAWK", "isClockChanged")
isClockChanged.set(false)
clockChangeListeners.forEach { it.onCurrentClockChanged() }
}
@@ -270,10 +275,8 @@
return
}
- android.util.Log.e("HAWK", "triggerOnAvailableClocksChanged")
scope.launch(mainDispatcher) {
assertMainThread()
- android.util.Log.e("HAWK", "isClockListChanged")
isClockListChanged.set(false)
clockChangeListeners.forEach { it.onAvailableClocksChanged() }
}
@@ -356,7 +359,7 @@
}
private var isVerifying = AtomicBoolean(false)
- private fun verifyLoadedProviders() {
+ fun verifyLoadedProviders() {
val shouldSchedule = isVerifying.compareAndSet(false, true)
if (!shouldSchedule) {
return
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index b49afee..4b94707 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -28,9 +28,10 @@
<FrameLayout
android:id="@+id/lockscreen_clock_view"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/small_clock_height"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
+ android:clipChildren="false"
android:paddingStart="@dimen/clock_padding_start"
android:visibility="invisible" />
<FrameLayout
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 1f44f05..cad2c16 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -95,6 +95,7 @@
<dimen name="num_pad_key_margin_end">12dp</dimen>
<!-- additional offset for clock switch area items -->
+ <dimen name="small_clock_height">114dp</dimen>
<dimen name="clock_padding_start">28dp</dimen>
<dimen name="below_clock_padding_start">32dp</dimen>
<dimen name="below_clock_padding_end">16dp</dimen>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index b9e711e..d8967d4 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -77,6 +77,29 @@
app:layout_constraintStart_toStartOf="parent"/>
<Button
+ android:id="@+id/rearrange"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <Button
+ android:id="@+id/addControls"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:text="@string/controls_favorite_add_controls"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <Button
android:id="@+id/done"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 79948da..4f6e88c 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -170,11 +170,11 @@
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
- android:background="@drawable/ripple_drawable"
android:contentDescription="@string/notification_more_settings"
+ android:background="@drawable/ripple_drawable_20dp"
android:src="@drawable/ic_settings"
- android:layout_alignParentEnd="true"
- android:tint="@color/notification_guts_link_icon_tint"/>
+ android:tint="?android:attr/colorAccent"
+ android:layout_alignParentEnd="true" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4d6c2022..852db1b 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -108,11 +108,11 @@
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
- android:background="@android:color/transparent"
android:contentDescription="@string/notification_more_settings"
- android:src="@drawable/notif_settings_button"
- android:layout_alignParentEnd="true"
- android:tint="@color/notification_guts_link_icon_tint"/>
+ android:background="@drawable/ripple_drawable_20dp"
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/colorAccent"
+ android:layout_alignParentEnd="true" />
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 9ed3f92..4850b35 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -81,11 +81,11 @@
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
- android:background="@drawable/ripple_drawable"
android:contentDescription="@string/notification_more_settings"
+ android:background="@drawable/ripple_drawable_20dp"
android:src="@drawable/ic_settings"
- android:layout_alignParentEnd="true"
- android:tint="@color/notification_guts_link_icon_tint"/>
+ android:tint="?android:attr/colorAccent"
+ android:layout_alignParentEnd="true"/>
</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 324ba02..1dd12ee 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2471,6 +2471,15 @@
<!-- Controls management favorites screen. See other apps button [CHAR LIMIT=30] -->
<string name="controls_favorite_see_other_apps">See other apps</string>
+ <!-- Controls management favorites screen. Rearrange controls button [CHAR LIMIT=30]-->
+ <string name="controls_favorite_rearrange_button">Rearrange</string>
+
+ <!-- Controls management edit screen. Add controls button [CHAR LIMIT=30]-->
+ <string name="controls_favorite_add_controls">Add controls</string>
+
+ <!-- Controls management edit screen. Return to editing button [CHAR LIMIT=30]-->
+ <string name="controls_favorite_back_to_editing">Back to editing</string>
+
<!-- Controls management controls screen error on load message [CHAR LIMIT=NONE] -->
<string name="controls_favorite_load_error">Controls could not be loaded. Check the <xliff:g id="app" example="System UI">%s</xliff:g> app to make sure that the app settings haven\u2019t changed.</string>
<!-- Controls management controls screen no controls found on load message [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 0890465..fac2f91 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -133,6 +133,10 @@
return this.baseIntent.getPackage();
}
+ public int getId() {
+ return id;
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof TaskKey)) {
@@ -307,6 +311,10 @@
lastSnapshotData.set(rawTask.lastSnapshotData);
}
+ public TaskKey getKey() {
+ return key;
+ }
+
/**
* Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
*/
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 53fab69..cab54d0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -66,7 +66,6 @@
import java.io.PrintWriter;
import java.util.Optional;
-import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -244,7 +243,12 @@
mListenersRegistered = false;
- mContext.unregisterReceiver(mDockedReceiver);
+ try {
+ mContext.unregisterReceiver(mDockedReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Docked receiver already unregistered", e);
+ }
+
if (mRotationWatcherRegistered) {
try {
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index e08a604..4269530 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -59,6 +59,8 @@
InteractionJankMonitor.CUJ_RECENTS_SCROLLING;
public static final int CUJ_APP_SWIPE_TO_RECENTS =
InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
+ public static final int CUJ_OPEN_SEARCH_RESULT =
+ InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -72,7 +74,8 @@
CUJ_APP_SWIPE_TO_RECENTS,
CUJ_OPEN_ALL_APPS,
CUJ_CLOSE_ALL_APPS_SWIPE,
- CUJ_CLOSE_ALL_APPS_TO_HOME
+ CUJ_CLOSE_ALL_APPS_TO_HOME,
+ CUJ_OPEN_SEARCH_RESULT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 6c59a94..4d7d0ea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -116,28 +116,25 @@
public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
// Device dreaming state
public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
- // Whether the screen is currently on. Note that the screen is considered on while turning on,
- // but not while turning off.
- public static final int SYSUI_STATE_SCREEN_ON = 1 << 28;
- // Whether the screen is currently transitioning into the state indicated by
- // SYSUI_STATE_SCREEN_ON.
- public static final int SYSUI_STATE_SCREEN_TRANSITION = 1 << 29;
+ // Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle).
+ // Note that the device is awake on while waking up on, but not while going to sleep.
+ public static final int SYSUI_STATE_AWAKE = 1 << 28;
+ // Whether the device is currently transitioning between awake/asleep indicated by
+ // SYSUI_STATE_AWAKE.
+ public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
// The notification panel expansion fraction is > 0
public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
- // Mask for SystemUiStateFlags to isolate SYSUI_STATE_SCREEN_ON and
- // SYSUI_STATE_SCREEN_TRANSITION, to match SCREEN_STATE_*
- public static final int SYSUI_STATE_SCREEN_STATE_MASK =
- SYSUI_STATE_SCREEN_ON | SYSUI_STATE_SCREEN_TRANSITION;
- // Screen is off.
- public static final int SCREEN_STATE_OFF = 0;
- // Screen is on.
- public static final int SCREEN_STATE_ON = SYSUI_STATE_SCREEN_ON;
- // Screen is still on, but transitioning to turn off.
- public static final int SCREEN_STATE_TURNING_OFF = SYSUI_STATE_SCREEN_TRANSITION;
- // Screen was off and is now turning on.
- public static final int SCREEN_STATE_TURNING_ON =
- SYSUI_STATE_SCREEN_TRANSITION | SYSUI_STATE_SCREEN_ON;
+ // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
+ // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
+ public static final int SYSUI_STATE_WAKEFULNESS_MASK =
+ SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION;
+ // Mirroring the WakefulnessLifecycle#Wakefulness states
+ public static final int WAKEFULNESS_ASLEEP = 0;
+ public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
+ public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
+ public static final int WAKEFULNESS_WAKING =
+ SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
// Whether the back gesture is allowed (or ignored) by the Shade
public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
@@ -172,8 +169,9 @@
SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
SYSUI_STATE_DEVICE_DREAMING,
- SYSUI_STATE_SCREEN_ON,
- SYSUI_STATE_SCREEN_TRANSITION,
+ SYSUI_STATE_AWAKE,
+ SYSUI_STATE_WAKEFULNESS_TRANSITION,
+ SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
})
public @interface SystemUiStateFlags {}
@@ -195,7 +193,7 @@
str.add("navbar_hidden");
}
if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0) {
- str.add("notif_visible");
+ str.add("notif_expanded");
}
if ((flags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) != 0) {
str.add("qs_visible");
@@ -263,11 +261,14 @@
if ((flags & SYSUI_STATE_DEVICE_DREAMING) != 0) {
str.add("device_dreaming");
}
- if ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0) {
- str.add("screen_transition");
+ if ((flags & SYSUI_STATE_WAKEFULNESS_TRANSITION) != 0) {
+ str.add("wakefulness_transition");
}
- if ((flags & SYSUI_STATE_SCREEN_ON) != 0) {
- str.add("screen_on");
+ if ((flags & SYSUI_STATE_AWAKE) != 0) {
+ str.add("awake");
+ }
+ if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0) {
+ str.add("notif_visible");
}
return str.toString();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 44f9d43..f094102 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -63,6 +63,7 @@
final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
for (int i = 0; i < info.getChanges().size(); i++) {
TransitionInfo.Change change = info.getChanges().get(i);
+ if (TransitionUtil.isOrderOnly(change)) continue;
if (filter.test(change)) {
out.add(TransitionUtil.newTarget(
change, info.getChanges().size() - i, info, t, leashMap));
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 58e7747..1fbf743 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -89,7 +89,7 @@
}
}
};
- return new RemoteTransition(remote, appThread);
+ return new RemoteTransition(remote, appThread, "Recents");
}
/**
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 31234cf..c22d689 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -43,9 +43,8 @@
id: Int,
name: String,
namespace: String = "systemui",
- teamfood: Boolean = false
): ReleasedFlag {
- val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+ val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
checkForDupesAndAdd(flag)
return flag
}
@@ -55,7 +54,6 @@
@BoolRes resourceId: Int,
name: String,
namespace: String = "systemui",
- teamfood: Boolean = false
): ResourceBooleanFlag {
val flag =
ResourceBooleanFlag(
@@ -63,7 +61,7 @@
name = name,
namespace = namespace,
resourceId = resourceId,
- teamfood = teamfood
+ teamfood = false,
)
checkForDupesAndAdd(flag)
return flag
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 27c5699..5502da1 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -43,9 +43,8 @@
id: Int,
name: String,
namespace: String = "systemui",
- teamfood: Boolean = false
): ReleasedFlag {
- val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+ val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
flagMap[name] = flag
return flag
}
@@ -55,7 +54,6 @@
@BoolRes resourceId: Int,
name: String,
namespace: String = "systemui",
- teamfood: Boolean = false
): ResourceBooleanFlag {
val flag =
ResourceBooleanFlag(
@@ -63,7 +61,7 @@
name = name,
namespace = namespace,
resourceId = resourceId,
- teamfood = teamfood
+ teamfood = false,
)
flagMap[name] = flag
return flag
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 9f2333d8..1980f70 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -107,14 +107,7 @@
// start fresh
mDismissing = false;
mView.resetPasswordText(false /* animate */, false /* announce */);
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (shouldLockout(deadline)) {
- handleAttemptLockout(deadline);
- } else {
- resetState();
- }
+ resetState();
}
@Override
@@ -277,7 +270,12 @@
@Override
public void onResume(int reason) {
mResumed = true;
- reset();
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (shouldLockout(deadline)) {
+ handleAttemptLockout(deadline);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cdaed87..07333f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -390,6 +390,13 @@
PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
}
+
+ }
+
+ void updateKeyguardStatusViewOffset() {
+ // updateClockTargetRegions will call onTargetRegionChanged
+ // which will require the correct translationY property of keyguardStatusView after updating
+ mView.updateClockTargetRegions();
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 68b40ab..5c56aab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -260,20 +260,18 @@
mLockPatternView.setEnabled(true);
mLockPatternView.clearPattern();
- // if the user is currently locked out, enforce it.
- long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
- KeyguardUpdateMonitor.getCurrentUser());
- if (deadline != 0) {
- handleAttemptLockout(deadline);
- } else {
- displayDefaultSecurityMessage();
- }
+ displayDefaultSecurityMessage();
}
@Override
public void onResume(int reason) {
super.onResume(reason);
- reset();
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ }
}
@Override
@@ -300,34 +298,38 @@
@Override
public void showPromptReason(int reason) {
/// TODO: move all this logic into the MessageAreaController?
+ int resId = 0;
switch (reason) {
case PROMPT_REASON_RESTART:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
+ resId = R.string.kg_prompt_reason_restart_pattern;
break;
case PROMPT_REASON_TIMEOUT:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ resId = R.string.kg_prompt_reason_timeout_pattern;
break;
case PROMPT_REASON_DEVICE_ADMIN:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
+ resId = R.string.kg_prompt_reason_device_admin;
break;
case PROMPT_REASON_USER_REQUEST:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
+ resId = R.string.kg_prompt_reason_user_request;
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ resId = R.string.kg_prompt_reason_timeout_pattern;
break;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ resId = R.string.kg_prompt_reason_timeout_pattern;
break;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ resId = R.string.kg_prompt_reason_timeout_pattern;
break;
case PROMPT_REASON_NONE:
break;
default:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ resId = R.string.kg_prompt_reason_timeout_pattern;
break;
}
+ if (resId != 0) {
+ mMessageAreaController.setMessage(getResources().getText(resId), /* animate= */ false);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 559db76..ded1238 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -127,6 +127,11 @@
@Override
public void onResume(int reason) {
super.onResume(reason);
+ // It's possible to reach a state here where mPasswordEntry believes it is focused
+ // but it is not actually focused. This state will prevent the view from gaining focus,
+ // as requestFocus will no-op since the focus flag is already set. By clearing focus first,
+ // it's guaranteed that the view has focus.
+ mPasswordEntry.clearFocus();
mPasswordEntry.requestFocus();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 67874e1..87a7758 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -40,6 +40,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
@@ -64,6 +65,7 @@
import com.android.keyguard.KeyguardSecurityContainer.SwipeListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.settingslib.Utils;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
@@ -634,6 +636,16 @@
mKeyguardStateController.isFaceAuthEnabled());
}
+ /** Sets an initial message that would override the default message */
+ public void setInitialMessage() {
+ CharSequence customMessage = mViewMediatorCallback.consumeCustomMessage();
+ if (!TextUtils.isEmpty(customMessage)) {
+ showMessage(customMessage, Utils.getColorError(getContext()));
+ return;
+ }
+ showPromptReason(mViewMediatorCallback.getBouncerPromptReason());
+ }
+
/**
* Show the bouncer and start appear animations.
*
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index f4c5815..fd55d69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -172,11 +172,15 @@
* Update position of the view with an optional animation
*/
public void updatePosition(int x, int y, float scale, boolean animate) {
+ float oldY = mView.getY();
PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
animate);
mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES,
animate);
+ if (oldY != y) {
+ mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a678edc..ac0a3fd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.AnimatorHandle;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -47,6 +48,7 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mAnimateYPos;
private boolean mKeyguardViewVisibilityAnimating;
+ private AnimatorHandle mKeyguardAnimatorHandle;
private boolean mLastOccludedState = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
private final LogBuffer mLogBuffer;
@@ -83,6 +85,10 @@
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
+ if (mKeyguardAnimatorHandle != null) {
+ mKeyguardAnimatorHandle.cancel();
+ mKeyguardAnimatorHandle = null;
+ }
mView.animate().cancel();
boolean isOccluded = mKeyguardStateController.isOccluded();
mKeyguardViewVisibilityAnimating = false;
@@ -116,7 +122,7 @@
.setDuration(320)
.setInterpolator(Interpolators.ALPHA_IN)
.withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
- log("keyguardFadingAway transition w/ Y Aniamtion");
+ log("keyguardFadingAway transition w/ Y Animation");
} else if (statusBarState == KEYGUARD) {
if (keyguardFadingAway) {
mKeyguardViewVisibilityAnimating = true;
@@ -148,7 +154,7 @@
// Ask the screen off animation controller to animate the keyguard visibility for us
// since it may need to be cancelled due to keyguard lifecycle events.
- mScreenOffAnimationController.animateInKeyguard(
+ mKeyguardAnimatorHandle = mScreenOffAnimationController.animateInKeyguard(
mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
} else {
log("Direct set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 1ae380e..235a8bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,7 +22,6 @@
import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -127,8 +126,6 @@
private boolean mCanDismissLockScreen;
private int mStatusBarState;
private boolean mIsKeyguardShowing;
- private boolean mUserUnlockedWithBiometric;
- private Runnable mCancelDelayedUpdateVisibilityRunnable;
private Runnable mOnGestureDetectedRunnable;
private Runnable mLongPressCancelRunnable;
@@ -229,7 +226,6 @@
updateIsUdfpsEnrolled();
updateConfiguration();
updateKeyguardShowing();
- mUserUnlockedWithBiometric = false;
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
mIsDozing = mStatusBarStateController.isDozing();
@@ -270,11 +266,6 @@
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
- if (mCancelDelayedUpdateVisibilityRunnable != null) {
- mCancelDelayedUpdateVisibilityRunnable.run();
- mCancelDelayedUpdateVisibilityRunnable = null;
- }
-
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
}
@@ -288,11 +279,6 @@
}
private void updateVisibility() {
- if (mCancelDelayedUpdateVisibilityRunnable != null) {
- mCancelDelayedUpdateVisibilityRunnable.run();
- mCancelDelayedUpdateVisibilityRunnable = null;
- }
-
if (!mIsKeyguardShowing && !mIsDozing) {
mView.setVisibility(View.INVISIBLE);
return;
@@ -300,9 +286,9 @@
boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
&& !mShowAodUnlockedIcon && !mShowAodLockIcon;
- mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
+ mShowLockIcon = !mCanDismissLockScreen && isLockScreen()
&& (!mUdfpsEnrolled || !mRunningFPS);
- mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
+ mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
@@ -426,7 +412,6 @@
pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
+ mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
- pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
pw.println(" mRunningFPS: " + mRunningFPS);
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
@@ -469,17 +454,6 @@
}
}
- /**
- * @return whether the userUnlockedWithBiometric state changed
- */
- private boolean updateUserUnlockedWithBiometric() {
- final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
- mUserUnlockedWithBiometric =
- mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
- KeyguardUpdateMonitor.getCurrentUser());
- return wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric;
- }
-
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
@@ -516,36 +490,15 @@
}
@Override
- public void onBiometricsCleared() {
- if (updateUserUnlockedWithBiometric()) {
- updateVisibility();
- }
- }
-
- @Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
final boolean wasRunningFps = mRunningFPS;
- final boolean userUnlockedWithBiometricChanged =
- updateUserUnlockedWithBiometric();
if (biometricSourceType == FINGERPRINT) {
mRunningFPS = running;
- if (wasRunningFps && !mRunningFPS) {
- if (mCancelDelayedUpdateVisibilityRunnable != null) {
- mCancelDelayedUpdateVisibilityRunnable.run();
- }
-
- // For some devices, auth is cancelled immediately on screen off but
- // before dozing state is set. We want to avoid briefly showing the
- // button in this case, so we delay updating the visibility by 50ms.
- mCancelDelayedUpdateVisibilityRunnable =
- mExecutor.executeDelayed(() -> updateVisibility(), 50);
- return;
- }
}
- if (userUnlockedWithBiometricChanged || wasRunningFps != mRunningFPS) {
+ if (wasRunningFps != mRunningFPS) {
updateVisibility();
}
}
@@ -556,7 +509,6 @@
@Override
public void onUnlockedChanged() {
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
- updateUserUnlockedWithBiometric();
updateKeyguardShowing();
updateVisibility();
}
@@ -573,9 +525,6 @@
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
updateKeyguardShowing();
- if (mIsKeyguardShowing) {
- updateUserUnlockedWithBiometric();
- }
updateVisibility();
}
@@ -694,7 +643,7 @@
private void onLongPress() {
cancelTouches();
- if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+ if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index cbc0a1b..ac30311 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -25,7 +25,6 @@
import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
import android.content.BroadcastReceiver;
@@ -619,9 +618,9 @@
}
logBiometricTouch(processedTouch.getEvent(), data);
- // Always pilfer pointers that are within sensor area
- if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)) {
- Log.d("Austin", "pilferTouch invalid overlap");
+ // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
+ if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
+ || mAlternateBouncerInteractor.isVisibleState()) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
}
@@ -983,7 +982,7 @@
}
if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
- if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+ if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 414c2ec..f876aff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -353,10 +353,19 @@
flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
}
- // Original sensorBounds assume portrait mode.
+ val isEnrollment = when (requestReason) {
+ REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
+ else -> false
+ }
+
+ // Use expanded overlay unless touchExploration enabled
var rotatedBounds =
if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
- Rect(overlayParams.overlayBounds)
+ if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
+ Rect(overlayParams.sensorBounds)
+ } else {
+ Rect(overlayParams.overlayBounds)
+ }
} else {
Rect(overlayParams.sensorBounds)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 063b41e..5101ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -167,6 +167,9 @@
private val keyguardStateControllerCallback: KeyguardStateController.Callback =
object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ updatePauseAuth()
+ }
override fun onLaunchTransitionFadingAwayChanged() {
launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
updatePauseAuth()
@@ -403,6 +406,15 @@
if (isBouncerExpansionGreaterThan(.5f)) {
return true
}
+ if (
+ keyguardUpdateMonitor.getUserUnlockedWithBiometric(
+ KeyguardUpdateMonitor.getCurrentUser()
+ )
+ ) {
+ // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid
+ // overlap with the LockIconView. Shortly afterwards, UDFPS will stop running.
+ return true
+ }
return view.unpausedAlpha < 255 * .1
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index 701df89..334cf93 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -41,7 +41,6 @@
public static final int SHADE_DRAG = 11;
public static final int QS_COLLAPSE = 12;
public static final int UDFPS_AUTHENTICATION = 13;
- public static final int LOCK_ICON = 14;
public static final int QS_SWIPE_SIDE = 15;
public static final int BACK_GESTURE = 16;
public static final int QS_SWIPE_NESTED = 17;
@@ -58,12 +57,10 @@
GENERIC,
BOUNCER_UNLOCK,
PULSE_EXPAND,
- BRIGHTNESS_SLIDER,
SHADE_DRAG,
QS_COLLAPSE,
BRIGHTNESS_SLIDER,
UDFPS_AUTHENTICATION,
- LOCK_ICON,
QS_SWIPE_SIDE,
QS_SWIPE_NESTED,
BACK_GESTURE,
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
index d17eadd..8ec48b9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
@@ -19,7 +19,6 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
import android.provider.DeviceConfig;
@@ -73,8 +72,7 @@
}
if (interactionType == LEFT_AFFORDANCE
- || interactionType == RIGHT_AFFORDANCE
- || interactionType == LOCK_ICON) {
+ || interactionType == RIGHT_AFFORDANCE) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index f8ee49a..15e2e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -158,7 +158,6 @@
|| interactionType == SHADE_DRAG
|| interactionType == QS_COLLAPSE
|| interactionType == Classifier.UDFPS_AUTHENTICATION
- || interactionType == Classifier.LOCK_ICON
|| interactionType == Classifier.QS_SWIPE_SIDE
|| interactionType == QS_SWIPE_NESTED) {
return Result.passed(0);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index d8d2c98..2fb6aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -47,8 +47,7 @@
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
- if (interactionType == Classifier.UDFPS_AUTHENTICATION
- || interactionType == Classifier.LOCK_ICON) {
+ if (interactionType == Classifier.UDFPS_AUTHENTICATION) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index 840982c..4a3710b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -21,7 +21,6 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
-import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR;
import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
@@ -93,8 +92,7 @@
double historyBelief, double historyConfidence) {
if (interactionType == BRIGHTNESS_SLIDER
|| interactionType == MEDIA_SEEKBAR
- || interactionType == SHADE_DRAG
- || interactionType == LOCK_ICON) {
+ || interactionType == SHADE_DRAG) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index e049ae0..c312f69 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -308,7 +308,7 @@
if (model.isSensitive()) {
mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
} else {
- mView.showTextPreview(model.getText(), false);
+ mView.showTextPreview(model.getText().toString(), false);
}
mView.setEditAccessibilityAction(true);
mOnPreviewTapped = this::editText;
@@ -527,7 +527,7 @@
}
private void showEditableText(CharSequence text, boolean hidden) {
- mView.showTextPreview(text, hidden);
+ mView.showTextPreview(text.toString(), hidden);
mView.setEditAccessibilityAction(true);
mOnPreviewTapped = this::editText;
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index ac1150e..e8c97bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -31,7 +31,6 @@
import android.util.ArrayMap
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_USER_ID
import com.android.systemui.Dumpable
import com.android.systemui.backup.BackupHelper
import com.android.systemui.controls.ControlStatus
@@ -44,7 +43,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
index 00a406e..be428a8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt
@@ -75,9 +75,12 @@
} else {
favoriteIds.remove(controlId)
}
- if (changed && !modified) {
- modified = true
- controlsModelCallback.onFirstChange()
+ if (changed) {
+ if (!modified) {
+ modified = true
+ controlsModelCallback.onFirstChange()
+ }
+ controlsModelCallback.onChange()
}
toChange?.let {
it.controlStatus.favorite = favorite
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 7df0865..d629e3e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -27,6 +27,7 @@
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import android.widget.Toast
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
@@ -38,8 +39,9 @@
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
-import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -48,17 +50,19 @@
* Activity for rearranging and removing controls for a given structure
*/
open class ControlsEditingActivity @Inject constructor(
+ featureFlags: FeatureFlags,
@Main private val mainExecutor: Executor,
private val controller: ControlsControllerImpl,
private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
- private val uiController: ControlsUiController
) : ComponentActivity() {
companion object {
private const val DEBUG = false
private const val TAG = "ControlsEditingActivity"
const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+ const val EXTRA_APP = ControlsFavoritingActivity.EXTRA_APP
+ const val EXTRA_FROM_FAVORITING = "extra_from_favoriting"
private val SUBTITLE_ID = R.string.controls_favorite_rearrange
private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
}
@@ -68,7 +72,12 @@
private lateinit var model: FavoritesModel
private lateinit var subtitle: TextView
private lateinit var saveButton: View
+ private lateinit var addControls: View
+ private var isFromFavoriting: Boolean = false
+
+ private val isNewFlowEnabled: Boolean =
+ featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
@@ -93,7 +102,7 @@
intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let {
component = it
} ?: run(this::finish)
-
+ isFromFavoriting = intent.getBooleanExtra(EXTRA_FROM_FAVORITING, false)
intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
structure = it
} ?: run(this::finish)
@@ -165,8 +174,42 @@
}
private fun bindButtons() {
+ addControls = requireViewById<Button>(R.id.addControls).apply {
+ isEnabled = true
+ visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+ setOnClickListener {
+ if (saveButton.isEnabled) {
+ // The user has made changes
+ Toast.makeText(
+ applicationContext,
+ R.string.controls_favorite_toast_no_changes,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ if (isFromFavoriting) {
+ animateExitAndFinish()
+ } else {
+ startActivity(Intent(context, ControlsFavoritingActivity::class.java).also {
+ it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
+ it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+ it.putExtra(
+ ControlsFavoritingActivity.EXTRA_APP,
+ intent.getCharSequenceExtra(EXTRA_APP),
+ )
+ it.putExtra(
+ ControlsFavoritingActivity.EXTRA_SOURCE,
+ ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
+ )
+ },
+ ActivityOptions.makeSceneTransitionAnimation(
+ this@ControlsEditingActivity
+ ).toBundle(),
+ )
+ }
+ }
+ }
saveButton = requireViewById<Button>(R.id.done).apply {
- isEnabled = false
+ isEnabled = isFromFavoriting
setText(R.string.save)
setOnClickListener {
saveFavorites()
@@ -194,6 +237,8 @@
}
}
+ override fun onChange() = Unit
+
override fun onFirstChange() {
saveButton.isEnabled = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 3e97d31..d3ffc95 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -37,6 +37,7 @@
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
+import androidx.annotation.VisibleForTesting
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
@@ -45,20 +46,20 @@
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
-import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import java.text.Collator
import java.util.concurrent.Executor
-import java.util.function.Consumer
import javax.inject.Inject
open class ControlsFavoritingActivity @Inject constructor(
+ featureFlags: FeatureFlags,
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
private val userTracker: UserTracker,
- private val uiController: ControlsUiController
) : ComponentActivity() {
companion object {
@@ -71,7 +72,10 @@
// If provided, show this structure page first
const val EXTRA_STRUCTURE = "extra_structure"
const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
- const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
+ const val EXTRA_SOURCE = "extra_source"
+ const val EXTRA_SOURCE_UNDEFINED: Byte = 0
+ const val EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR: Byte = 1
+ const val EXTRA_SOURCE_VALUE_FROM_EDITING: Byte = 2
private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
private const val TOOLTIP_MAX_SHOWN = 2
}
@@ -79,7 +83,7 @@
private var component: ComponentName? = null
private var appName: CharSequence? = null
private var structureExtra: CharSequence? = null
- private var fromProviderSelector = false
+ private var openSource = EXTRA_SOURCE_UNDEFINED
private lateinit var structurePager: ViewPager2
private lateinit var statusText: TextView
@@ -89,12 +93,19 @@
private var mTooltipManager: TooltipManager? = null
private lateinit var doneButton: View
private lateinit var otherAppsButton: View
+ private lateinit var rearrangeButton: Button
private var listOfStructures = emptyList<StructureContainer>()
private lateinit var comparator: Comparator<StructureContainer>
private var cancelLoadRunnable: Runnable? = null
private var isPagerLoaded = false
+ private val fromProviderSelector: Boolean
+ get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
+ private val fromEditing: Boolean
+ get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
+ private val isNewFlowEnabled: Boolean =
+ featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
@@ -117,14 +128,20 @@
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
if (serviceInfos.size > 1) {
- otherAppsButton.post {
- otherAppsButton.visibility = View.VISIBLE
+ val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE
+ if (otherAppsButton.visibility != newVisibility) {
+ otherAppsButton.post {
+ otherAppsButton.visibility = newVisibility
+ }
}
}
}
}
override fun onBackPressed() {
+ if (fromEditing) {
+ animateExitAndFinish()
+ }
if (!fromProviderSelector) {
openControlsOrigin()
}
@@ -139,7 +156,7 @@
appName = intent.getCharSequenceExtra(EXTRA_APP)
structureExtra = intent.getCharSequenceExtra(EXTRA_STRUCTURE)
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
- fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false)
+ openSource = intent.getByteExtra(EXTRA_SOURCE, EXTRA_SOURCE_UNDEFINED)
bindViews()
}
@@ -148,14 +165,19 @@
override fun onFirstChange() {
doneButton.isEnabled = true
}
+
+ override fun onChange() {
+ val structure: StructureContainer = listOfStructures[structurePager.currentItem]
+ rearrangeButton.isEnabled = structure.model.favorites.isNotEmpty()
+ }
}
private fun loadControls() {
- component?.let {
+ component?.let { componentName ->
statusText.text = resources.getText(com.android.internal.R.string.loading)
val emptyZoneString = resources.getText(
R.string.controls_favorite_other_zone_header)
- controller.loadForComponent(it, Consumer { data ->
+ controller.loadForComponent(componentName, { data ->
val allControls = data.allControls
val favoriteKeys = data.favoritesIds
val error = data.errorOnLoad
@@ -213,7 +235,7 @@
ControlsAnimations.enterAnimation(structurePager).start()
}
}
- }, Consumer { runnable -> cancelLoadRunnable = runnable })
+ }, { runnable -> cancelLoadRunnable = runnable })
}
}
@@ -299,7 +321,8 @@
bindButtons()
}
- private fun animateExitAndFinish() {
+ @VisibleForTesting
+ internal open fun animateExitAndFinish() {
val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
ControlsAnimations.exitAnimation(
rootView,
@@ -312,6 +335,32 @@
}
private fun bindButtons() {
+ rearrangeButton = requireViewById<Button>(R.id.rearrange).apply {
+ text = if (fromEditing) {
+ getString(R.string.controls_favorite_back_to_editing)
+ } else {
+ getString(R.string.controls_favorite_rearrange_button)
+ }
+ isEnabled = false
+ visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+ setOnClickListener {
+ if (component == null) return@setOnClickListener
+ saveFavorites()
+ startActivity(
+ Intent(context, ControlsEditingActivity::class.java).also {
+ it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
+ it.putExtra(ControlsEditingActivity.EXTRA_APP, appName)
+ it.putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, true)
+ it.putExtra(
+ ControlsEditingActivity.EXTRA_STRUCTURE,
+ listOfStructures[structurePager.currentItem].structureName,
+ )
+ },
+ ActivityOptions
+ .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
+ )
+ }
+ }
otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
setOnClickListener {
if (doneButton.isEnabled) {
@@ -335,18 +384,22 @@
isEnabled = false
setOnClickListener {
if (component == null) return@setOnClickListener
- listOfStructures.forEach {
- val favoritesForStorage = it.model.favorites
- controller.replaceFavoritesForStructure(
- StructureInfo(component!!, it.structureName, favoritesForStorage)
- )
- }
+ saveFavorites()
animateExitAndFinish()
openControlsOrigin()
}
}
}
+ private fun saveFavorites() {
+ listOfStructures.forEach {
+ val favoritesForStorage = it.model.favorites
+ controller.replaceFavoritesForStructure(
+ StructureInfo(component!!, it.structureName, favoritesForStorage)
+ )
+ }
+ }
+
private fun openControlsOrigin() {
startActivity(
Intent(applicationContext, ControlsActivity::class.java),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
index d65481a..3455e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt
@@ -71,6 +71,11 @@
* Use to notify that the model has changed for the first time
*/
fun onFirstChange()
+
+ /**
+ * Use to notify that the model has changed
+ */
+ fun onChange()
}
/**
@@ -132,7 +137,7 @@
controlInfo: ControlInfo,
favorite: Boolean,
customIconGetter: (ComponentName, String) -> Icon?
- ): this(component, controlInfo, favorite) {
+ ) : this(component, controlInfo, favorite) {
this.customIconGetter = customIconGetter
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 3808e73..92aff06 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -211,7 +211,10 @@
putExtra(ControlsFavoritingActivity.EXTRA_APP,
listingController.getAppLabel(it))
putExtra(Intent.EXTRA_COMPONENT_NAME, it)
- putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
+ putExtra(
+ ControlsFavoritingActivity.EXTRA_SOURCE,
+ ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
+ )
}
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
animateExitAndFinish()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
index 0aeb128..cf0dcad 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import java.util.concurrent.Executor;
+
/**
* Forwards the currently used brightness to {@link DozeHost}.
*/
@@ -23,8 +25,9 @@
private final DozeHost mHost;
- public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host) {
- super(wrappedService);
+ public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host,
+ Executor bgExecutor) {
+ super(wrappedService, bgExecutor);
mHost = host;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index f0aefb5..7f0b16b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -39,6 +39,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -150,7 +151,6 @@
private final DockManager mDockManager;
private final Part[] mParts;
private final UserTracker mUserTracker;
-
private final ArrayList<State> mQueuedRequests = new ArrayList<>();
private State mState = State.UNINITIALIZED;
private int mPulseReason;
@@ -512,9 +512,11 @@
class Delegate implements Service {
private final Service mDelegate;
+ private final Executor mBgExecutor;
- public Delegate(Service delegate) {
+ public Delegate(Service delegate, Executor bgExecutor) {
mDelegate = delegate;
+ mBgExecutor = bgExecutor;
}
@Override
@@ -534,7 +536,9 @@
@Override
public void setDozeScreenBrightness(int brightness) {
- mDelegate.setDozeScreenBrightness(brightness);
+ mBgExecutor.execute(() -> {
+ mDelegate.setDozeScreenBrightness(brightness);
+ });
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 25c2c39..8d44472 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -22,14 +22,16 @@
import com.android.systemui.statusbar.phone.DozeParameters;
+import java.util.concurrent.Executor;
+
/**
* Prevents usage of doze screen states on devices that don't support them.
*/
public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
@VisibleForTesting
- DozeScreenStatePreventingAdapter(DozeMachine.Service inner) {
- super(inner);
+ DozeScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
+ super(inner, bgExecutor);
}
@Override
@@ -47,8 +49,8 @@
* return a new instance of {@link DozeScreenStatePreventingAdapter} wrapping {@code inner}.
*/
public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
- DozeParameters params) {
- return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner) : inner;
+ DozeParameters params, Executor bgExecutor) {
+ return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner, bgExecutor) : inner;
}
private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
index a0c490951..f7773f1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
@@ -22,14 +22,16 @@
import com.android.systemui.statusbar.phone.DozeParameters;
+import java.util.concurrent.Executor;
+
/**
* Prevents usage of doze screen states on devices that don't support them.
*/
public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
@VisibleForTesting
- DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner) {
- super(inner);
+ DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
+ super(inner, bgExecutor);
}
@Override
@@ -45,8 +47,9 @@
* return a new instance of {@link DozeSuspendScreenStatePreventingAdapter} wrapping {@code inner}.
*/
public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
- DozeParameters params) {
- return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner) : inner;
+ DozeParameters params, Executor bgExecutor) {
+ return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner, bgExecutor)
+ : inner;
}
private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 069344f..d408472 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -22,6 +22,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.doze.DozeAuthRemover;
import com.android.systemui.doze.DozeBrightnessHostForwarder;
import com.android.systemui.doze.DozeDockHandler;
@@ -45,13 +46,14 @@
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import dagger.Module;
+import dagger.Provides;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
-
-import dagger.Module;
-import dagger.Provides;
+import java.util.concurrent.Executor;
/** Dagger module for use with {@link com.android.systemui.doze.dagger.DozeComponent}. */
@Module
@@ -60,13 +62,13 @@
@DozeScope
@WrappedService
static DozeMachine.Service providesWrappedService(DozeMachine.Service dozeMachineService,
- DozeHost dozeHost, DozeParameters dozeParameters) {
+ DozeHost dozeHost, DozeParameters dozeParameters, @UiBackground Executor bgExecutor) {
DozeMachine.Service wrappedService = dozeMachineService;
- wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost);
+ wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost, bgExecutor);
wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
- wrappedService, dozeParameters);
+ wrappedService, dozeParameters, bgExecutor);
wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
- wrappedService, dozeParameters);
+ wrappedService, dozeParameters, bgExecutor);
return wrappedService;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 74a49a8..c954f98 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -201,8 +201,6 @@
mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
-
- mTouchInsetSession.addViewToTracking(mView);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 43e4c62..7f44463 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -101,6 +101,10 @@
completer.set(predecessor);
}
+
+ if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) {
+ stopMonitoring(false);
+ }
});
return "DreamOverlayTouchMonitor::pop";
@@ -214,7 +218,12 @@
@Override
public void onPause(@NonNull LifecycleOwner owner) {
- stopMonitoring();
+ stopMonitoring(false);
+ }
+
+ @Override
+ public void onDestroy(LifecycleOwner owner) {
+ stopMonitoring(true);
}
};
@@ -222,7 +231,7 @@
* When invoked, instantiates a new {@link InputSession} to monitor touch events.
*/
private void startMonitoring() {
- stopMonitoring();
+ stopMonitoring(true);
mCurrentInputSession = mInputSessionFactory.create(
"dreamOverlay",
mInputEventListener,
@@ -234,11 +243,16 @@
/**
* Destroys any active {@link InputSession}.
*/
- private void stopMonitoring() {
+ private void stopMonitoring(boolean force) {
if (mCurrentInputSession == null) {
return;
}
+ if (!mActiveTouchSessions.isEmpty() && !force) {
+ mStopMonitoringPending = true;
+ return;
+ }
+
// When we stop monitoring touches, we must ensure that all active touch sessions and
// descendants informed of the removal so any cleanup for active tracking can proceed.
mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
@@ -250,6 +264,7 @@
mCurrentInputSession.dispose();
mCurrentInputSession = null;
+ mStopMonitoringPending = false;
}
@@ -257,6 +272,8 @@
private final Collection<DreamTouchHandler> mHandlers;
private final DisplayHelper mDisplayHelper;
+ private boolean mStopMonitoringPending;
+
private InputChannelCompat.InputEventListener mInputEventListener =
new InputChannelCompat.InputEventListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
new file mode 100644
index 0000000..58b70b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -0,0 +1,92 @@
+/*
+ * 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.dreams.touch;
+
+import static com.android.systemui.dreams.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
+ * to bring down the shade.
+ */
+public class ShadeTouchHandler implements DreamTouchHandler {
+ private final Optional<CentralSurfaces> mSurfaces;
+ private final int mInitiationHeight;
+
+ @Inject
+ ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
+ @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
+ mSurfaces = centralSurfaces;
+ mInitiationHeight = initiationHeight;
+ }
+
+ @Override
+ public void onSessionStart(TouchSession session) {
+ if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
+ session.pop();
+ return;
+ }
+
+ session.registerInputListener(ev -> {
+ final NotificationPanelViewController viewController =
+ mSurfaces.map(CentralSurfaces::getNotificationPanelViewController).orElse(null);
+
+ if (viewController != null) {
+ viewController.handleExternalTouch((MotionEvent) ev);
+ }
+
+ if (ev instanceof MotionEvent) {
+ if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+ session.pop();
+ }
+ }
+ });
+
+ session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void getTouchInitiationRegion(Rect bounds, Region region) {
+ final Rect outBounds = new Rect(bounds);
+ outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
+ region.op(outBounds, Region.Op.UNION);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
index dad0004..b719126 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
@@ -23,6 +23,7 @@
*/
@Module(includes = {
BouncerSwipeModule.class,
+ ShadeModule.class,
}, subcomponents = {
InputSessionComponent.class,
})
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
new file mode 100644
index 0000000..9e0ae41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.touch.dagger;
+
+import android.content.res.Resources;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.dreams.touch.ShadeTouchHandler;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+
+import javax.inject.Named;
+
+/**
+ * Dependencies for swipe down to notification over dream.
+ */
+@Module
+public class ShadeModule {
+ /**
+ * The height, defined in pixels, of the gesture initiation region at the top of the screen for
+ * swiping down notifications.
+ */
+ public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
+ "notification_shade_gesture_initiation_height";
+
+ /**
+ * Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
+ */
+ @Provides
+ @IntoSet
+ public static DreamTouchHandler providesNotificationShadeTouchHandler(
+ ShadeTouchHandler touchHandler) {
+ return touchHandler;
+ }
+
+ /**
+ * Provides the height of the gesture area for notification swipe down.
+ */
+ @Provides
+ @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+ public static int providesNotificationShadeGestureRegionHeight(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 095ae38..0cd2791 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -101,16 +101,16 @@
releasedFlag(174148361, "notification_inline_reply_animation")
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
- releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
+ releasedFlag(254647461, "filter_unseen_notifs_on_keyguard")
// TODO(b/263414400): Tracking Bug
@JvmField
val NOTIFICATION_ANIMATE_BIG_PICTURE =
- releasedFlag(120, "notification_animate_big_picture", teamfood = true)
+ releasedFlag(120, "notification_animate_big_picture")
@JvmField
val ANIMATED_NOTIFICATION_SHADE_INSETS =
- unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+ releasedFlag(270682168, "animated_notification_shade_insets")
// TODO(b/268005230): Tracking Bug
@JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
@@ -184,7 +184,7 @@
// flag for controlling auto pin confirmation and material u shapes in bouncer
@JvmField
val AUTO_PIN_CONFIRMATION =
- releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation", teamfood = true)
+ releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
// TODO(b/262859270): Tracking Bug
@JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
@@ -621,15 +621,15 @@
@JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
// 2000 - device controls
- @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
+ @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels")
@JvmField
val APP_PANELS_ALL_APPS_ALLOWED =
- releasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
+ releasedFlag(2001, "app_panels_all_apps_allowed")
@JvmField
val CONTROLS_MANAGEMENT_NEW_FLOWS =
- releasedFlag(2002, "controls_management_new_flows", teamfood = true)
+ releasedFlag(2002, "controls_management_new_flows")
// Enables removing app from Home control panel as a part of a new flow
// TODO(b/269132640): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
new file mode 100644
index 0000000..801b165
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -0,0 +1,493 @@
+package com.android.systemui.graphics
+
+import android.annotation.AnyThread
+import android.annotation.DrawableRes
+import android.annotation.Px
+import android.annotation.SuppressLint
+import android.annotation.WorkerThread
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.content.res.Resources.NotFoundException
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.ImageDecoder.DecodeException
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.util.Size
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * Helper class to load images for SystemUI. It allows for memory efficient image loading with size
+ * restriction and attempts to use hardware bitmaps when sensible.
+ */
+@SysUISingleton
+class ImageLoader
+@Inject
+constructor(
+ private val defaultContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher
+) {
+
+ /** Source of the image data. */
+ sealed interface Source
+
+ /**
+ * Load image from a Resource ID. If the resource is part of another package or if it requires
+ * tinting, pass in a correct [Context].
+ */
+ data class Res(@DrawableRes val resId: Int, val context: Context?) : Source {
+ constructor(@DrawableRes resId: Int) : this(resId, null)
+ }
+
+ /** Load image from a Uri. */
+ data class Uri(val uri: android.net.Uri) : Source {
+ constructor(uri: String) : this(android.net.Uri.parse(uri))
+ }
+
+ /** Load image from a [File]. */
+ data class File(val file: java.io.File) : Source {
+ constructor(path: String) : this(java.io.File(path))
+ }
+
+ /** Load image from an [InputStream]. */
+ data class InputStream(val inputStream: java.io.InputStream, val context: Context?) : Source {
+ constructor(inputStream: java.io.InputStream) : this(inputStream, null)
+ }
+
+ /**
+ * Loads passed [Source] on a background thread and returns the [Bitmap].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+ * ratio.
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadBitmap(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? =
+ withContext(backgroundDispatcher) { loadBitmapSync(source, maxWidth, maxHeight, allocator) }
+
+ /**
+ * Loads passed [Source] synchronously and returns the [Bitmap].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints while keeping aspect
+ * ratio.
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadBitmapSync(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? {
+ return try {
+ loadBitmapSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
+ }
+
+ /**
+ * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Bitmap] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadBitmapSync(
+ source: ImageDecoder.Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Bitmap? {
+ return try {
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
+ /**
+ * Loads passed [Source] on a background thread and returns the [Drawable].
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadDrawable(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+
+ /**
+ * Loads passed [Icon] on a background thread and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param context Alternate context to use for resource loading (for e.g. cross-process use)
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @AnyThread
+ suspend fun loadDrawable(
+ icon: Icon,
+ context: Context = defaultContext,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ loadDrawableSync(icon, context, maxWidth, maxHeight, allocator)
+ }
+
+ /**
+ * Loads passed [Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @WorkerThread
+ @SuppressLint("UseCompatLoadingForDrawables")
+ fun loadDrawableSync(
+ source: Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return try {
+ loadDrawableSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ ?:
+ // If we have a resource, retry fallback using the "normal" Resource loading system.
+ // This will come into effect in cases like trying to load AnimatedVectorDrawable.
+ if (source is Res) {
+ val context = source.context ?: defaultContext
+ ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+ } else {
+ null
+ }
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
+ }
+
+ /**
+ * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
+ *
+ * Maximum height and width can be passed as optional parameters - the image decoder will make
+ * sure to keep the decoded drawable size within those passed constraints (while keeping aspect
+ * ratio).
+ *
+ * @param maxWidth Maximum width of the returned drawable (if able). 0 means no restriction. Set
+ * to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param maxHeight Maximum height of the returned drawable (if able). 0 means no restriction.
+ * Set to [DEFAULT_MAX_SAFE_BITMAP_SIZE_PX] by default.
+ * @param allocator Allocator to use for the loaded drawable - one of [ImageDecoder] allocator
+ * ints. Use [ImageDecoder.ALLOCATOR_SOFTWARE] to force software bitmap.
+ * @return loaded [Drawable] or `null` if loading failed.
+ */
+ @WorkerThread
+ fun loadDrawableSync(
+ source: ImageDecoder.Source,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return try {
+ ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
+ /** Loads icon drawable while attempting to size restrict the drawable. */
+ @WorkerThread
+ fun loadDrawableSync(
+ icon: Icon,
+ context: Context = defaultContext,
+ @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
+ allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+ ): Drawable? {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+ Icon.TYPE_RESOURCE -> {
+ val resources = resolveResourcesForIcon(context, icon)
+ resources?.let {
+ loadDrawableSync(
+ ImageDecoder.createSource(it, icon.resId),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
+ // is a Vector drawable which ImageDecoder doesn't support.)
+ ?: icon.loadDrawable(context)
+ }
+ Icon.TYPE_BITMAP -> {
+ BitmapDrawable(context.resources, icon.bitmap)
+ }
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+ }
+ Icon.TYPE_DATA -> {
+ loadDrawableSync(
+ ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ else -> {
+ // We don't recognize this icon, just fallback.
+ icon.loadDrawable(context)
+ }
+ }?.let { drawable ->
+ // Icons carry tint which we need to propagate down to a Drawable.
+ tintDrawable(icon, drawable)
+ drawable
+ }
+ }
+
+ companion object {
+ const val TAG = "ImageLoader"
+
+ // 4096 is a reasonable default - most devices will support 4096x4096 texture size for
+ // Canvas rendering and by default we SystemUI has no need to render larger bitmaps.
+ // This prevents exceptions and crashes if the code accidentally loads larger Bitmap
+ // and then attempts to render it on Canvas.
+ // It can always be overridden by the parameters.
+ const val DEFAULT_MAX_SAFE_BITMAP_SIZE_PX = 4096
+
+ /**
+ * This constant signals that ImageLoader shouldn't attempt to resize the passed bitmap in a
+ * given dimension.
+ *
+ * Set both maxWidth and maxHeight to [DO_NOT_RESIZE] if you wish to prevent resizing.
+ */
+ const val DO_NOT_RESIZE = 0
+
+ /** Maps [Source] to [ImageDecoder.Source]. */
+ private fun toImageDecoderSource(source: Source, defaultContext: Context) =
+ when (source) {
+ is Res -> {
+ val context = source.context ?: defaultContext
+ ImageDecoder.createSource(context.resources, source.resId)
+ }
+ is File -> ImageDecoder.createSource(source.file)
+ is Uri -> ImageDecoder.createSource(defaultContext.contentResolver, source.uri)
+ is InputStream -> {
+ val context = source.context ?: defaultContext
+ ImageDecoder.createSource(context.resources, source.inputStream)
+ }
+ }
+
+ /**
+ * This sets target size on the image decoder to conform to the maxWidth / maxHeight
+ * parameters. The parameters are chosen to keep the existing drawable aspect ratio.
+ */
+ @AnyThread
+ private fun configureDecoderForMaximumSize(
+ decoder: ImageDecoder,
+ imgSize: Size,
+ @Px maxWidth: Int,
+ @Px maxHeight: Int
+ ) {
+ if (maxWidth == DO_NOT_RESIZE && maxHeight == DO_NOT_RESIZE) {
+ return
+ }
+
+ if (imgSize.width <= maxWidth && imgSize.height <= maxHeight) {
+ return
+ }
+
+ // Determine the scale factor for each dimension so it fits within the set constraint
+ val wScale =
+ if (maxWidth <= 0) {
+ 1.0f
+ } else {
+ maxWidth.toFloat() / imgSize.width.toFloat()
+ }
+
+ val hScale =
+ if (maxHeight <= 0) {
+ 1.0f
+ } else {
+ maxHeight.toFloat() / imgSize.height.toFloat()
+ }
+
+ // Scale down to the dimension that demands larger scaling (smaller scale factor).
+ // Use the same scale for both dimensions to keep the aspect ratio.
+ val scale = min(wScale, hScale)
+ if (scale < 1.0f) {
+ val targetWidth = (imgSize.width * scale).toInt()
+ val targetHeight = (imgSize.height * scale).toInt()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Configured image size to $targetWidth x $targetHeight")
+ }
+
+ decoder.setTargetSize(targetWidth, targetHeight)
+ }
+ }
+
+ /**
+ * Attempts to retrieve [Resources] class required to load the passed icon. Icons can
+ * originate from other processes so we need to make sure we load them from the right
+ * package source.
+ *
+ * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+ * the resource package couldn't be resolved.
+ */
+ @WorkerThread
+ private fun resolveResourcesForIcon(context: Context, icon: Icon): Resources? {
+ if (icon.type != Icon.TYPE_RESOURCE) {
+ return null
+ }
+
+ val resources = icon.resources
+ if (resources != null) {
+ return resources
+ }
+
+ val resPackage = icon.resPackage
+ if (
+ resPackage == null || resPackage.isEmpty() || context.packageName.equals(resPackage)
+ ) {
+ return context.resources
+ }
+
+ if ("android" == resPackage) {
+ return Resources.getSystem()
+ }
+
+ val pm = context.packageManager
+ try {
+ val ai =
+ pm.getApplicationInfo(
+ resPackage,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES or
+ PackageManager.GET_SHARED_LIBRARY_FILES
+ )
+ if (ai != null) {
+ return pm.getResourcesForApplication(ai)
+ } else {
+ Log.w(TAG, "Failed to resolve application info for $resPackage")
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Failed to resolve resource package", e)
+ return null
+ }
+ return null
+ }
+
+ /** Applies tinting from [Icon] to the passed [Drawable]. */
+ @AnyThread
+ private fun tintDrawable(icon: Icon, drawable: Drawable) {
+ if (icon.hasTint()) {
+ drawable.mutate()
+ drawable.setTintList(icon.tintList)
+ drawable.setTintBlendMode(icon.tintBlendMode)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index eef7ccc..107e685 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -231,22 +231,20 @@
);
}
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) {
+ public void mergeAnimation(IBinder candidateTransition, TransitionInfo candidateInfo,
+ SurfaceControl.Transaction candidateT, IBinder currentTransition,
+ IRemoteTransitionFinishedCallback candidateFinishCallback) {
try {
- final IRemoteTransitionFinishedCallback origFinishCB;
+ final IRemoteTransitionFinishedCallback currentFinishCB;
synchronized (mFinishCallbacks) {
- origFinishCB = mFinishCallbacks.remove(transition);
+ currentFinishCB = mFinishCallbacks.remove(currentTransition);
}
- info.releaseAllSurfaces();
- t.close();
- if (origFinishCB == null) {
- // already finished (or not started yet), so do nothing.
+ if (currentFinishCB == null) {
+ Slog.e(TAG, "Called mergeAnimation, but finish callback is missing");
return;
}
runner.onAnimationCancelled(false /* isKeyguardOccluded */);
- origFinishCB.onTransitionFinished(null /* wct */, null /* t */);
+ currentFinishCB.onTransitionFinished(null /* wct */, null /* t */);
} catch (RemoteException e) {
// nothing, we'll just let it finish on its own I guess.
}
@@ -304,13 +302,13 @@
Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY");
TransitionFilter f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
- mShellTransitions.registerRemote(f,
- new RemoteTransition(wrap(mExitAnimationRunner), getIApplicationThread()));
+ mShellTransitions.registerRemote(f, new RemoteTransition(
+ wrap(mExitAnimationRunner), getIApplicationThread(), "ExitKeyguard"));
Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
// Register for occluding
final RemoteTransition occludeTransition = new RemoteTransition(
- mOccludeAnimation, getIApplicationThread());
+ mOccludeAnimation, getIApplicationThread(), "KeyguardOcclude");
f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
f.mRequirements = new TransitionFilter.Requirement[]{
@@ -329,7 +327,7 @@
// Now register for un-occlude.
final RemoteTransition unoccludeTransition = new RemoteTransition(
- mUnoccludeAnimation, getIApplicationThread());
+ mUnoccludeAnimation, getIApplicationThread(), "KeyguardUnocclude");
f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
f.mRequirements = new TransitionFilter.Requirement[]{
@@ -384,7 +382,7 @@
f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
mShellTransitions.registerRemote(f, new RemoteTransition(
wrap(mKeyguardViewMediator.getOccludeByDreamAnimationRunner()),
- getIApplicationThread()));
+ getIApplicationThread(), "KeyguardOccludeByDream"));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 364e79c..ea5fc1a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -953,10 +953,15 @@
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
- if (mOccludeByDreamAnimator != null) {
- mOccludeByDreamAnimator.cancel();
- }
- setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
+ mContext.getMainExecutor().execute(() -> {
+ if (mOccludeByDreamAnimator != null) {
+ mOccludeByDreamAnimator.cancel();
+ }
+ });
+ // The value of isKeyguardOccluded here may come from mergeAnimation, which
+ // isn't reliable. In all cases, after running or cancelling this animation,
+ // keyguard should be occluded.
+ setOccluded(true /* isOccluded */, false /* animate */);
if (DEBUG) {
Log.d(TAG, "Occlude by Dream animation cancelled. Occluded state is now: "
+ mOccluded);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 4cdcafd..8764f12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -44,6 +44,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
+import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
@@ -67,8 +68,6 @@
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
/**
* Dagger Module providing keyguard.
@@ -83,6 +82,7 @@
KeyguardDataQuickAffordanceModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
+ KeyguardFaceAuthModule.class,
StartKeyguardTransitionModule.class,
})
public class KeyguardModule {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index d5129a6..09002fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -87,6 +87,13 @@
*/
val isStrongBiometricAllowed: StateFlow<Boolean>
+ /**
+ * Whether the current user is allowed to use a convenience biometric for device entry based on
+ * Android Security policies. If false, the user may be able to use strong biometric or primary
+ * authentication for device entry.
+ */
+ val isNonStrongBiometricAllowed: StateFlow<Boolean>
+
/** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
@@ -276,6 +283,16 @@
)
)
+ override val isNonStrongBiometricAllowed: StateFlow<Boolean> =
+ strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ strongAuthTracker.isBiometricAllowedForUser(
+ false,
+ userRepository.getSelectedUserInfo().id
+ )
+ )
+
override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
selectedUserId
.flatMapLatest { userId ->
@@ -297,40 +314,62 @@
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
LockPatternUtils.StrongAuthTracker(context) {
- private val _authFlags =
+ // Backing field for onStrongAuthRequiredChanged
+ private val _strongAuthFlags =
MutableStateFlow(
StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
)
+ // Backing field for onIsNonStrongBiometricAllowedChanged
+ private val _nonStrongBiometricAllowed =
+ MutableStateFlow(
+ Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
+ )
+
val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
userRepository.selectedUserInfo
.map { it.id }
.distinctUntilChanged()
- .flatMapLatest { currUserId ->
- _authFlags
- .filter { it.userId == currUserId }
+ .flatMapLatest { userId ->
+ _strongAuthFlags
+ .filter { it.userId == userId }
.onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
.onStart {
- emit(
- StrongAuthenticationFlags(
- currentUserId,
- getStrongAuthForUser(currentUserId)
- )
- )
+ emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
}
}
+ /** isStrongBiometricAllowed for the current user. */
val isStrongBiometricAllowed: Flow<Boolean> =
currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) }
+ /** isNonStrongBiometricAllowed for the current user. */
+ val isNonStrongBiometricAllowed: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .distinctUntilChanged()
+ .flatMapLatest { userId ->
+ _nonStrongBiometricAllowed
+ .filter { it.first == userId }
+ .map { it.second }
+ .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") }
+ .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) }
+ }
+
private val currentUserId
get() = userRepository.getSelectedUserInfo().id
override fun onStrongAuthRequiredChanged(userId: Int) {
val newFlags = getStrongAuthForUser(userId)
- _authFlags.value = StrongAuthenticationFlags(userId, newFlags)
+ _strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
}
+
+ override fun onIsNonStrongBiometricAllowedChanged(userId: Int) {
+ val allowed = isNonStrongBiometricAllowedAfterIdleTimeout(userId)
+ _nonStrongBiometricAllowed.value = Pair(userId, allowed)
+ Log.d(TAG, "onIsNonStrongBiometricAllowedChanged for userId: $userId, $allowed")
+ }
}
private fun DevicePolicyManager.isFaceDisabled(userId: Int): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..56e7398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.hardware.face.FaceAuthenticateOptions
+import android.hardware.face.FaceManager
+import android.os.CancellationSignal
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+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.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.user.data.repository.UserRepository
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * API to run face authentication and detection for device entry / on keyguard (as opposed to the
+ * biometric prompt).
+ */
+interface DeviceEntryFaceAuthRepository {
+ /** Provide the current face authentication state for device entry. */
+ val isAuthenticated: Flow<Boolean>
+
+ /** Whether face auth can run at this point. */
+ val canRunFaceAuth: Flow<Boolean>
+
+ /** Provide the current status of face authentication. */
+ val authenticationStatus: Flow<AuthenticationStatus>
+
+ /** Provide the current status of face detection. */
+ val detectionStatus: Flow<DetectionStatus>
+
+ /** Current state of whether face authentication is locked out or not. */
+ val isLockedOut: Flow<Boolean>
+
+ /** Current state of whether face authentication is running. */
+ val isAuthRunning: Flow<Boolean>
+
+ /**
+ * Trigger face authentication.
+ *
+ * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
+ * ignored if face authentication is already running. Results should be propagated through
+ * [authenticationStatus]
+ *
+ * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
+ */
+ suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
+
+ /** Stop currently running face authentication or detection. */
+ fun cancel()
+}
+
+@SysUISingleton
+class DeviceEntryFaceAuthRepositoryImpl
+@Inject
+constructor(
+ context: Context,
+ private val faceManager: FaceManager? = null,
+ private val userRepository: UserRepository,
+ private val keyguardBypassController: KeyguardBypassController? = null,
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val sessionTracker: SessionTracker,
+ private val uiEventsLogger: UiEventLogger,
+ private val faceAuthLogger: FaceAuthenticationLogger,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
+ private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val trustRepository: TrustRepository,
+ private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ dumpManager: DumpManager,
+) : DeviceEntryFaceAuthRepository, Dumpable {
+ private var authCancellationSignal: CancellationSignal? = null
+ private var detectCancellationSignal: CancellationSignal? = null
+ private var faceAcquiredInfoIgnoreList: Set<Int>
+
+ private var cancelNotReceivedHandlerJob: Job? = null
+
+ private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+ MutableStateFlow(null)
+ override val authenticationStatus: Flow<AuthenticationStatus>
+ get() = _authenticationStatus.filterNotNull()
+
+ private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = _detectionStatus.filterNotNull()
+
+ private val _isLockedOut = MutableStateFlow(false)
+ override val isLockedOut: StateFlow<Boolean> = _isLockedOut
+
+ val isDetectionSupported =
+ faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
+
+ private val _isAuthRunning = MutableStateFlow(false)
+ override val isAuthRunning: StateFlow<Boolean>
+ get() = _isAuthRunning
+
+ private val keyguardSessionId: InstanceId?
+ get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
+
+ private val _canRunFaceAuth = MutableStateFlow(true)
+ override val canRunFaceAuth: StateFlow<Boolean>
+ get() = _canRunFaceAuth
+
+ private val canRunDetection = MutableStateFlow(false)
+
+ private val _isAuthenticated = MutableStateFlow(false)
+ override val isAuthenticated: Flow<Boolean>
+ get() = _isAuthenticated
+
+ private val bypassEnabled: Flow<Boolean> =
+ keyguardBypassController?.let {
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardBypassController.OnBypassStateChangedListener {
+ override fun onBypassStateChanged(isEnabled: Boolean) {
+ trySendWithFailureLogging(isEnabled, TAG, "BypassStateChanged")
+ }
+ }
+ it.registerOnBypassStateChangedListener(callback)
+ trySendWithFailureLogging(it.bypassEnabled, TAG, "BypassStateChanged")
+ awaitClose { it.unregisterOnBypassStateChangedListener(callback) }
+ }
+ }
+ ?: flowOf(false)
+
+ private val faceLockoutResetCallback =
+ object : FaceManager.LockoutResetCallback() {
+ override fun onLockoutReset(sensorId: Int) {
+ _isLockedOut.value = false
+ }
+ }
+
+ init {
+ faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
+ faceAcquiredInfoIgnoreList =
+ Arrays.stream(
+ context.resources.getIntArray(
+ R.array.config_face_acquire_device_entry_ignorelist
+ )
+ )
+ .boxed()
+ .collect(Collectors.toSet())
+ dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
+
+ observeFaceAuthGatingChecks()
+ observeFaceDetectGatingChecks()
+ observeFaceAuthResettingConditions()
+ }
+
+ private fun observeFaceAuthResettingConditions() {
+ // Clear auth status when keyguard is going away or when the user is switching.
+ merge(keyguardRepository.isKeyguardGoingAway, userRepository.userSwitchingInProgress)
+ .onEach { goingAwayOrUserSwitchingInProgress ->
+ if (goingAwayOrUserSwitchingInProgress) {
+ _isAuthenticated.value = false
+ }
+ }
+ .launchIn(applicationScope)
+ }
+
+ private fun observeFaceDetectGatingChecks() {
+ // Face detection can run only when lockscreen bypass is enabled
+ // & detection is supported & biometric unlock is not allowed.
+ listOf(
+ canFaceAuthOrDetectRun(),
+ logAndObserve(bypassEnabled, "bypassEnabled"),
+ logAndObserve(
+ biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
+ "nonStrongBiometricIsNotAllowed"
+ ),
+ // We don't want to run face detect if it's not possible to authenticate with FP
+ // from the bouncer. UDFPS is the only fp sensor type that won't support this.
+ logAndObserve(
+ and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
+ "udfpsAuthIsNotPossibleAnymore"
+ )
+ )
+ .reduce(::and)
+ .distinctUntilChanged()
+ .onEach {
+ faceAuthLogger.canRunDetectionChanged(it)
+ canRunDetection.value = it
+ if (!it) {
+ cancelDetection()
+ }
+ }
+ .launchIn(applicationScope)
+ }
+
+ private fun isUdfps() =
+ deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
+ it == BiometricType.UNDER_DISPLAY_FINGERPRINT
+ }
+
+ private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+ return listOf(
+ logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+ logAndObserve(
+ biometricSettingsRepository.isFaceAuthenticationEnabled,
+ "isFaceAuthenticationEnabled"
+ ),
+ logAndObserve(
+ userRepository.userSwitchingInProgress.isFalse(),
+ "userSwitchingNotInProgress"
+ ),
+ logAndObserve(
+ keyguardRepository.isKeyguardGoingAway.isFalse(),
+ "keyguardNotGoingAway"
+ ),
+ logAndObserve(
+ keyguardRepository.wakefulness
+ .map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
+ .isFalse(),
+ "deviceNotSleepingOrNotStartingToSleep"
+ ),
+ logAndObserve(
+ combine(
+ keyguardInteractor.isSecureCameraActive,
+ alternateBouncerInteractor.isVisible,
+ ) { a, b ->
+ !a || b
+ },
+ "secureCameraNotActiveOrAltBouncerIsShowing"
+ ),
+ logAndObserve(
+ biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
+ "isFaceAuthSupportedInCurrentPosture"
+ ),
+ logAndObserve(
+ biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
+ "userHasNotLockedDownDevice"
+ )
+ )
+ .reduce(::and)
+ }
+
+ private fun observeFaceAuthGatingChecks() {
+ // Face auth can run only if all of the gating conditions are true.
+ listOf(
+ canFaceAuthOrDetectRun(),
+ logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+ logAndObserve(
+ deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
+ "fpLockedOut"
+ ),
+ logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
+ logAndObserve(
+ biometricSettingsRepository.isNonStrongBiometricAllowed,
+ "nonStrongBiometricIsAllowed"
+ ),
+ logAndObserve(
+ userRepository.selectedUserInfo.map { it.isPrimary },
+ "userIsPrimaryUser"
+ ),
+ )
+ .reduce(::and)
+ .distinctUntilChanged()
+ .onEach {
+ faceAuthLogger.canFaceAuthRunChanged(it)
+ _canRunFaceAuth.value = it
+ if (!it) {
+ // Cancel currently running auth if any of the gating checks are false.
+ faceAuthLogger.cancellingFaceAuth()
+ cancel()
+ }
+ }
+ .launchIn(applicationScope)
+ }
+
+ private val faceAuthCallback =
+ object : FaceManager.AuthenticationCallback() {
+ override fun onAuthenticationFailed() {
+ _authenticationStatus.value = FailedAuthenticationStatus
+ _isAuthenticated.value = false
+ faceAuthLogger.authenticationFailed()
+ onFaceAuthRequestCompleted()
+ }
+
+ override fun onAuthenticationAcquired(acquireInfo: Int) {
+ _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+ faceAuthLogger.authenticationAcquired(acquireInfo)
+ }
+
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
+ val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+ if (errorStatus.isLockoutError()) {
+ _isLockedOut.value = true
+ }
+ _authenticationStatus.value = errorStatus
+ _isAuthenticated.value = false
+ if (errorStatus.isCancellationError()) {
+ cancelNotReceivedHandlerJob?.cancel()
+ applicationScope.launch {
+ faceAuthLogger.launchingQueuedFaceAuthRequest(
+ faceAuthRequestedWhileCancellation
+ )
+ faceAuthRequestedWhileCancellation?.let { authenticate(it) }
+ faceAuthRequestedWhileCancellation = null
+ }
+ }
+ faceAuthLogger.authenticationError(
+ errorCode,
+ errString,
+ errorStatus.isLockoutError(),
+ errorStatus.isCancellationError()
+ )
+ onFaceAuthRequestCompleted()
+ }
+
+ override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
+ if (faceAcquiredInfoIgnoreList.contains(code)) {
+ return
+ }
+ _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+ }
+
+ override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
+ _authenticationStatus.value = SuccessAuthenticationStatus(result)
+ _isAuthenticated.value = true
+ faceAuthLogger.faceAuthSuccess(result)
+ onFaceAuthRequestCompleted()
+ }
+ }
+
+ private fun onFaceAuthRequestCompleted() {
+ cancellationInProgress = false
+ _isAuthRunning.value = false
+ authCancellationSignal = null
+ }
+
+ private val detectionCallback =
+ FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
+ faceAuthLogger.faceDetected()
+ _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+ }
+
+ private var cancellationInProgress = false
+ private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
+
+ override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ if (_isAuthRunning.value) {
+ faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+ return
+ }
+
+ if (cancellationInProgress) {
+ faceAuthLogger.queuingRequestWhileCancelling(
+ faceAuthRequestedWhileCancellation,
+ uiEvent
+ )
+ faceAuthRequestedWhileCancellation = uiEvent
+ return
+ } else {
+ faceAuthRequestedWhileCancellation = null
+ }
+
+ if (canRunFaceAuth.value) {
+ withContext(mainDispatcher) {
+ // We always want to invoke face auth in the main thread.
+ authCancellationSignal = CancellationSignal()
+ _isAuthRunning.value = true
+ uiEventsLogger.logWithInstanceIdAndPosition(
+ uiEvent,
+ 0,
+ null,
+ keyguardSessionId,
+ uiEvent.extraInfo
+ )
+ faceAuthLogger.authenticating(uiEvent)
+ faceManager?.authenticate(
+ null,
+ authCancellationSignal,
+ faceAuthCallback,
+ null,
+ FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
+ )
+ }
+ } else if (fallbackToDetection && canRunDetection.value) {
+ detect()
+ }
+ }
+
+ suspend fun detect() {
+ if (!isDetectionSupported) {
+ faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
+ return
+ }
+ if (_isAuthRunning.value || detectCancellationSignal != null) {
+ faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
+ return
+ }
+
+ detectCancellationSignal = CancellationSignal()
+ withContext(mainDispatcher) {
+ // We always want to invoke face detect in the main thread.
+ faceAuthLogger.faceDetectionStarted()
+ faceManager?.detectFace(
+ detectCancellationSignal,
+ detectionCallback,
+ FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
+ )
+ }
+ }
+
+ private val currentUserId: Int
+ get() = userRepository.getSelectedUserInfo().id
+
+ fun cancelDetection() {
+ detectCancellationSignal?.cancel()
+ detectCancellationSignal = null
+ }
+
+ override fun cancel() {
+ if (authCancellationSignal == null) return
+
+ authCancellationSignal?.cancel()
+ cancelNotReceivedHandlerJob =
+ applicationScope.launch {
+ delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+ faceAuthLogger.cancelSignalNotReceived(
+ _isAuthRunning.value,
+ _isLockedOut.value,
+ cancellationInProgress,
+ faceAuthRequestedWhileCancellation
+ )
+ onFaceAuthRequestCompleted()
+ }
+ cancellationInProgress = true
+ _isAuthRunning.value = false
+ }
+
+ private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
+ return cond.distinctUntilChanged().onEach {
+ faceAuthLogger.observedConditionChanged(it, loggingContext)
+ }
+ }
+
+ companion object {
+ const val TAG = "DeviceEntryFaceAuthRepository"
+
+ /**
+ * If no cancel signal has been received after this amount of time, assume that it is
+ * cancelled.
+ */
+ const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("DeviceEntryFaceAuthRepositoryImpl state:")
+ pw.println(" cancellationInProgress: $cancellationInProgress")
+ pw.println(" _isLockedOut.value: ${_isLockedOut.value}")
+ pw.println(" _isAuthRunning.value: ${_isAuthRunning.value}")
+ pw.println(" isDetectionSupported: $isDetectionSupported")
+ pw.println(" FaceManager state:")
+ pw.println(" faceManager: $faceManager")
+ pw.println(" sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
+ pw.println(
+ " supportsFaceDetection: " +
+ "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
+ )
+ pw.println(
+ " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
+ )
+ pw.println(" authCancellationSignal: $authCancellationSignal")
+ pw.println(" detectCancellationSignal: $detectCancellationSignal")
+ pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
+ pw.println(" _authenticationStatus: ${_authenticationStatus.value}")
+ pw.println(" _detectionStatus: ${_detectionStatus.value}")
+ pw.println(" currentUserId: $currentUserId")
+ pw.println(" keyguardSessionId: $keyguardSessionId")
+ pw.println(" lockscreenBypassEnabled: ${keyguardBypassController?.bypassEnabled ?: false}")
+ }
+}
+/** Combine two boolean flows by and-ing both of them */
+private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+ flow.combine(anotherFlow) { a, b -> a && b }
+
+/** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
+private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
+ return this.map { !it }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 4fa56ee..52234b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.data.repository
+import android.hardware.biometrics.BiometricAuthenticator
+import android.hardware.biometrics.BiometricAuthenticator.Modality
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -33,6 +35,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/** Encapsulates state about device entry fingerprint auth mechanism. */
@@ -49,7 +52,7 @@
/**
* Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
*/
- val availableFpSensorType: BiometricType?
+ val availableFpSensorType: Flow<BiometricType?>
}
/**
@@ -77,11 +80,39 @@
pw.println("isLockedOut=${isLockedOut.value}")
}
- override val availableFpSensorType: BiometricType?
- get() =
- if (authController.isUdfpsSupported) BiometricType.UNDER_DISPLAY_FINGERPRINT
- else if (authController.isSfpsSupported) BiometricType.SIDE_FINGERPRINT
- else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
+ override val availableFpSensorType: Flow<BiometricType?>
+ get() {
+ return if (authController.areAllFingerprintAuthenticatorsRegistered()) {
+ flowOf(getFpSensorType())
+ } else {
+ conflatedCallbackFlow {
+ val callback =
+ object : AuthController.Callback {
+ override fun onAllAuthenticatorsRegistered(@Modality modality: Int) {
+ if (modality == BiometricAuthenticator.TYPE_FINGERPRINT)
+ trySendWithFailureLogging(
+ getFpSensorType(),
+ TAG,
+ "onAllAuthenticatorsRegistered, emitting fpSensorType"
+ )
+ }
+ }
+ authController.addCallback(callback)
+ trySendWithFailureLogging(
+ getFpSensorType(),
+ TAG,
+ "initial value for fpSensorType"
+ )
+ awaitClose { authController.removeCallback(callback) }
+ }
+ }
+ }
+
+ private fun getFpSensorType(): BiometricType? {
+ return if (authController.isUdfpsSupported) BiometricType.UNDER_DISPLAY_FINGERPRINT
+ else if (authController.isSfpsSupported) BiometricType.SIDE_FINGERPRINT
+ else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
+ }
override val isLockedOut: StateFlow<Boolean> =
conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 64e2a2c..0b506cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -65,8 +65,6 @@
val keyguardAuthenticated: StateFlow<Boolean?>
val showMessage: StateFlow<BouncerShowMessageModel?>
val resourceUpdateRequests: StateFlow<Boolean>
- val bouncerPromptReason: Int
- val bouncerErrorMessage: CharSequence?
val alternateBouncerVisible: StateFlow<Boolean>
val alternateBouncerUIAvailable: StateFlow<Boolean>
val sideFpsShowing: StateFlow<Boolean>
@@ -145,11 +143,6 @@
override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
- override val bouncerPromptReason: Int
- get() = viewMediatorCallback.bouncerPromptReason
- override val bouncerErrorMessage: CharSequence?
- get() = viewMediatorCallback.consumeCustomMessage()
-
/** Values associated with the AlternateBouncer */
private val _alternateBouncerVisible = MutableStateFlow(false)
override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt
deleted file mode 100644
index a326840..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt
+++ /dev/null
@@ -1,346 +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.keyguard.data.repository
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.hardware.face.FaceAuthenticateOptions
-import android.hardware.face.FaceManager
-import android.os.CancellationSignal
-import com.android.internal.logging.InstanceId
-import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.Dumpable
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
-import com.android.systemui.log.FaceAuthenticationLogger
-import com.android.systemui.log.SessionTracker
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.user.data.repository.UserRepository
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-/**
- * API to run face authentication and detection for device entry / on keyguard (as opposed to the
- * biometric prompt).
- */
-interface KeyguardFaceAuthManager {
- /**
- * Trigger face authentication.
- *
- * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
- * ignored if face authentication is already running. Results should be propagated through
- * [authenticationStatus]
- */
- suspend fun authenticate(uiEvent: FaceAuthUiEvent)
-
- /**
- * Trigger face detection.
- *
- * Invocation should be ignored if face authentication is currently running.
- */
- suspend fun detect()
-
- /** Stop currently running face authentication or detection. */
- fun cancel()
-
- /** Provide the current status of face authentication. */
- val authenticationStatus: Flow<AuthenticationStatus>
-
- /** Provide the current status of face detection. */
- val detectionStatus: Flow<DetectionStatus>
-
- /** Current state of whether face authentication is locked out or not. */
- val isLockedOut: Flow<Boolean>
-
- /** Current state of whether face authentication is running. */
- val isAuthRunning: Flow<Boolean>
-
- /** Is face detection supported. */
- val isDetectionSupported: Boolean
-}
-
-@SysUISingleton
-class KeyguardFaceAuthManagerImpl
-@Inject
-constructor(
- context: Context,
- private val faceManager: FaceManager? = null,
- private val userRepository: UserRepository,
- private val keyguardBypassController: KeyguardBypassController? = null,
- @Application private val applicationScope: CoroutineScope,
- @Main private val mainDispatcher: CoroutineDispatcher,
- private val sessionTracker: SessionTracker,
- private val uiEventsLogger: UiEventLogger,
- private val faceAuthLogger: FaceAuthenticationLogger,
- dumpManager: DumpManager,
-) : KeyguardFaceAuthManager, Dumpable {
- private var cancellationSignal: CancellationSignal? = null
- private val lockscreenBypassEnabled: Boolean
- get() = keyguardBypassController?.bypassEnabled ?: false
- private var faceAcquiredInfoIgnoreList: Set<Int>
-
- private val faceLockoutResetCallback =
- object : FaceManager.LockoutResetCallback() {
- override fun onLockoutReset(sensorId: Int) {
- _isLockedOut.value = false
- }
- }
-
- init {
- faceManager?.addLockoutResetCallback(faceLockoutResetCallback)
- faceAcquiredInfoIgnoreList =
- Arrays.stream(
- context.resources.getIntArray(
- R.array.config_face_acquire_device_entry_ignorelist
- )
- )
- .boxed()
- .collect(Collectors.toSet())
- dumpManager.registerCriticalDumpable("KeyguardFaceAuthManagerImpl", this)
- }
-
- private val faceAuthCallback =
- object : FaceManager.AuthenticationCallback() {
- override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedAuthenticationStatus
- faceAuthLogger.authenticationFailed()
- onFaceAuthRequestCompleted()
- }
-
- override fun onAuthenticationAcquired(acquireInfo: Int) {
- _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
- faceAuthLogger.authenticationAcquired(acquireInfo)
- }
-
- override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
- val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
- if (errorStatus.isLockoutError()) {
- _isLockedOut.value = true
- }
- _authenticationStatus.value = errorStatus
- if (errorStatus.isCancellationError()) {
- cancelNotReceivedHandlerJob?.cancel()
- applicationScope.launch {
- faceAuthLogger.launchingQueuedFaceAuthRequest(
- faceAuthRequestedWhileCancellation
- )
- faceAuthRequestedWhileCancellation?.let { authenticate(it) }
- faceAuthRequestedWhileCancellation = null
- }
- }
- faceAuthLogger.authenticationError(
- errorCode,
- errString,
- errorStatus.isLockoutError(),
- errorStatus.isCancellationError()
- )
- onFaceAuthRequestCompleted()
- }
-
- override fun onAuthenticationHelp(code: Int, helpStr: CharSequence?) {
- if (faceAcquiredInfoIgnoreList.contains(code)) {
- return
- }
- _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
- }
-
- override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
- _authenticationStatus.value = SuccessAuthenticationStatus(result)
- faceAuthLogger.faceAuthSuccess(result)
- onFaceAuthRequestCompleted()
- }
- }
-
- private fun onFaceAuthRequestCompleted() {
- cancellationInProgress = false
- _isAuthRunning.value = false
- cancellationSignal = null
- }
-
- private val detectionCallback =
- FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
- faceAuthLogger.faceDetected()
- _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
- }
-
- private var cancellationInProgress = false
- private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
-
- override suspend fun authenticate(uiEvent: FaceAuthUiEvent) {
- if (_isAuthRunning.value) {
- faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
- return
- }
-
- if (cancellationInProgress) {
- faceAuthLogger.queuingRequestWhileCancelling(
- faceAuthRequestedWhileCancellation,
- uiEvent
- )
- faceAuthRequestedWhileCancellation = uiEvent
- return
- } else {
- faceAuthRequestedWhileCancellation = null
- }
-
- withContext(mainDispatcher) {
- // We always want to invoke face auth in the main thread.
- cancellationSignal = CancellationSignal()
- _isAuthRunning.value = true
- uiEventsLogger.logWithInstanceIdAndPosition(
- uiEvent,
- 0,
- null,
- keyguardSessionId,
- uiEvent.extraInfo
- )
- faceAuthLogger.authenticating(uiEvent)
- faceManager?.authenticate(
- null,
- cancellationSignal,
- faceAuthCallback,
- null,
- FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
- )
- }
- }
-
- override suspend fun detect() {
- if (!isDetectionSupported) {
- faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
- return
- }
- if (_isAuthRunning.value) {
- faceAuthLogger.skippingBecauseAlreadyRunning("detection")
- return
- }
-
- cancellationSignal = CancellationSignal()
- withContext(mainDispatcher) {
- // We always want to invoke face detect in the main thread.
- faceAuthLogger.faceDetectionStarted()
- faceManager?.detectFace(
- cancellationSignal,
- detectionCallback,
- FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
- )
- }
- }
-
- private val currentUserId: Int
- get() = userRepository.getSelectedUserInfo().id
-
- override fun cancel() {
- if (cancellationSignal == null) return
-
- cancellationSignal?.cancel()
- cancelNotReceivedHandlerJob =
- applicationScope.launch {
- delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
- faceAuthLogger.cancelSignalNotReceived(
- _isAuthRunning.value,
- _isLockedOut.value,
- cancellationInProgress,
- faceAuthRequestedWhileCancellation
- )
- onFaceAuthRequestCompleted()
- }
- cancellationInProgress = true
- _isAuthRunning.value = false
- }
-
- private var cancelNotReceivedHandlerJob: Job? = null
-
- private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
- MutableStateFlow(null)
- override val authenticationStatus: Flow<AuthenticationStatus>
- get() = _authenticationStatus.filterNotNull()
-
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
- get() = _detectionStatus.filterNotNull()
-
- private val _isLockedOut = MutableStateFlow(false)
- override val isLockedOut: Flow<Boolean> = _isLockedOut
-
- override val isDetectionSupported =
- faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
-
- private val _isAuthRunning = MutableStateFlow(false)
- override val isAuthRunning: Flow<Boolean>
- get() = _isAuthRunning
-
- private val keyguardSessionId: InstanceId?
- get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
-
- companion object {
- const val TAG = "KeyguardFaceAuthManager"
-
- /**
- * If no cancel signal has been received after this amount of time, assume that it is
- * cancelled.
- */
- const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("KeyguardFaceAuthManagerImpl state:")
- pw.println(" cancellationInProgress: $cancellationInProgress")
- pw.println(" _isLockedOut.value: ${_isLockedOut.value}")
- pw.println(" _isAuthRunning.value: ${_isAuthRunning.value}")
- pw.println(" isDetectionSupported: $isDetectionSupported")
- pw.println(" FaceManager state:")
- pw.println(" faceManager: $faceManager")
- pw.println(" sensorPropertiesInternal: ${faceManager?.sensorPropertiesInternal}")
- pw.println(
- " supportsFaceDetection: " +
- "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
- )
- pw.println(
- " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
- )
- pw.println(" cancellationSignal: $cancellationSignal")
- pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
- pw.println(" _authenticationStatus: ${_authenticationStatus.value}")
- pw.println(" _detectionStatus: ${_detectionStatus.value}")
- pw.println(" currentUserId: $currentUserId")
- pw.println(" keyguardSessionId: $keyguardSessionId")
- pw.println(" lockscreenBypassEnabled: $lockscreenBypassEnabled")
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
new file mode 100644
index 0000000..3c66f24
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardFaceAuthModule {
+ @Binds
+ fun deviceEntryFaceAuthRepository(
+ impl: DeviceEntryFaceAuthRepositoryImpl
+ ): DeviceEntryFaceAuthRepository
+
+ @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 33f4e2e..9212aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -27,7 +27,6 @@
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.settingslib.Utils
import com.android.systemui.DejankUtils
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
@@ -42,12 +41,12 @@
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import javax.inject.Inject
/**
* Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
@@ -82,12 +81,6 @@
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
repository.setPrimaryShow(true)
- primaryBouncerView.delegate?.showPromptReason(repository.bouncerPromptReason)
- (repository.bouncerErrorMessage as? String)?.let {
- repository.setShowMessage(
- BouncerShowMessageModel(message = it, Utils.getColorError(context))
- )
- }
repository.setPrimaryShowingSoon(false)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index b1c5f8f..eded9c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -18,7 +18,10 @@
import android.hardware.face.FaceManager
-/** Authentication status provided by [com.android.keyguard.faceauth.KeyguardFaceAuthManager] */
+/**
+ * Authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
+ */
sealed class AuthenticationStatus
/** Success authentication status. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 468a6b5..172f922 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -110,24 +110,24 @@
viewModel.setBouncerViewDelegate(delegate)
launch {
viewModel.isShowing.collect { isShowing ->
+ view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
if (isShowing) {
// Reset Security Container entirely.
securityContainerController.reinflateViewFlipper {
// Reset Security Container entirely.
- view.visibility = View.VISIBLE
securityContainerController.onBouncerVisibilityChanged(
/* isVisible= */ true
)
securityContainerController.showPrimarySecurityScreen(
/* turningOff= */ false
)
+ securityContainerController.setInitialMessage()
securityContainerController.appear()
securityContainerController.onResume(
KeyguardSecurityView.SCREEN_ON
)
}
} else {
- view.visibility = View.INVISIBLE
securityContainerController.onBouncerVisibilityChanged(
/* isVisible= */ false
)
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 647e3a1..f7355d5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -7,17 +7,17 @@
import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
-private const val TAG = "KeyguardFaceAuthManagerLog"
+private const val TAG = "DeviceEntryFaceAuthRepositoryLog"
/**
- * Helper class for logging for [com.android.keyguard.faceauth.KeyguardFaceAuthManager]
+ * Helper class for logging for
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
*
* To enable logcat echoing for an entire buffer:
* ```
- * adb shell settings put global systemui/buffer/KeyguardFaceAuthManagerLog <logLevel>
+ * adb shell settings put global systemui/buffer/DeviceEntryFaceAuthRepositoryLog <logLevel>
*
* ```
*/
@@ -82,8 +82,19 @@
)
}
- fun skippingBecauseAlreadyRunning(@CompileTimeConstant operation: String) {
- logBuffer.log(TAG, DEBUG, "isAuthRunning is true, skipping $operation")
+ fun skippingDetection(isAuthRunning: Boolean, detectCancellationNotNull: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = isAuthRunning
+ bool2 = detectCancellationNotNull
+ },
+ {
+ "Skipping running detection: isAuthRunning: $bool1, " +
+ "detectCancellationNotNull: $bool2"
+ }
+ )
}
fun faceDetectionStarted() {
@@ -177,4 +188,33 @@
{ "Face authenticated successfully: userId: $int1, isStrongBiometric: $bool1" }
)
}
+
+ fun observedConditionChanged(newValue: Boolean, context: String) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = newValue
+ str1 = context
+ },
+ { "Observed condition changed: $str1, new value: $bool1" }
+ )
+ }
+
+ fun canFaceAuthRunChanged(canRun: Boolean) {
+ logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" })
+ }
+
+ fun canRunDetectionChanged(canRunDetection: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { bool1 = canRunDetection },
+ { "canRunDetection value changed to $bool1" }
+ )
+ }
+
+ fun cancellingFaceAuth() {
+ logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index b98a92f..d848b43 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -28,6 +28,8 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -60,6 +62,7 @@
private final AuthController mAuthController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
+ private final UiEventLogger mUiEventLogger;
private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
private boolean mKeyguardSessionStarted;
@@ -69,12 +72,14 @@
IStatusBarService statusBarService,
AuthController authController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController
+ KeyguardStateController keyguardStateController,
+ UiEventLogger uiEventLogger
) {
mStatusBarManagerService = statusBarService;
mAuthController = authController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -116,6 +121,10 @@
}
private void endSession(int type) {
+ endSession(type, null);
+ }
+
+ private void endSession(int type, @Nullable SessionUiEvent endSessionUiEvent) {
if (mSessionToInstanceId.getOrDefault(type, null) == null) {
Log.e(TAG, "session [" + getString(type) + "] was not started");
return;
@@ -127,6 +136,9 @@
if (DEBUG) {
Log.d(TAG, "Session end for [" + getString(type) + "] id=" + instanceId);
}
+ if (endSessionUiEvent != null) {
+ mUiEventLogger.log(endSessionUiEvent, instanceId);
+ }
mStatusBarManagerService.onSessionEnded(type, instanceId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to send onSessionEnded for session="
@@ -139,7 +151,7 @@
@Override
public void onStartedGoingToSleep(int why) {
if (mKeyguardSessionStarted) {
- endSession(SESSION_KEYGUARD);
+ endSession(SESSION_KEYGUARD, SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP);
}
// Start a new session whenever the device goes to sleep
@@ -162,7 +174,8 @@
startSession(SESSION_KEYGUARD);
} else if (!keyguardShowing && wasSessionStarted) {
mKeyguardSessionStarted = false;
- endSession(SESSION_KEYGUARD);
+ endSession(SESSION_KEYGUARD,
+ SessionUiEvent.KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY);
}
}
};
@@ -200,4 +213,22 @@
return "unknownType=" + sessionType;
}
+
+ enum SessionUiEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "A keyguard session ended due to the keyguard going away.")
+ KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY(1354),
+
+ @UiEvent(doc = "A keyguard session ended due to display going to sleep.")
+ KEYGUARD_SESSION_END_GOING_TO_SLEEP(1355);
+
+ private final int mId;
+ SessionUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
}
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 e204def..3775e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -367,13 +367,13 @@
/**
* Provides a {@link LogBuffer} for use by
- * {@link com.android.keyguard.faceauth.KeyguardFaceAuthManagerImpl}.
+ * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
*/
@Provides
@SysUISingleton
@FaceAuthLog
public static LogBuffer provideFaceAuthLog(LogBufferFactory factory) {
- return factory.create("KeyguardFaceAuthManagerLog", 300);
+ return factory.create("DeviceEntryFaceAuthRepositoryLog", 300);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index f92a5ab..731bb2f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -66,6 +66,8 @@
protected final MediaOutputController mController;
+ private static final int UNMUTE_DEFAULT_VOLUME = 2;
+
Context mContext;
View mHolderView;
boolean mIsDragging;
@@ -193,10 +195,6 @@
mTwoLineTitleText.setTextColor(mController.getColorItemContent());
if (mController.isAdvancedLayoutSupported()) {
mVolumeValueText.setTextColor(mController.getColorItemContent());
- mTitleIcon.setOnTouchListener(((v, event) -> {
- mSeekBar.dispatchTouchEvent(event);
- return false;
- }));
}
mSeekBar.setProgressTintList(
ColorStateList.valueOf(mController.getColorSeekbarProgress()));
@@ -546,13 +544,21 @@
private void enableSeekBar(MediaDevice device) {
mSeekBar.setEnabled(true);
mSeekBar.setOnTouchListener((v, event) -> false);
- if (mController.isAdvancedLayoutSupported()) {
- updateIconAreaClickListener((v) -> {
+ updateIconAreaClickListener((v) -> {
+ if (device.getCurrentVolume() == 0) {
+ mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
+ updateUnmutedVolumeIcon();
+ mTitleIcon.setOnTouchListener(((iconV, event) -> false));
+ } else {
mSeekBar.resetVolume();
mController.adjustVolume(device, 0);
updateMutedVolumeIcon();
- });
- }
+ mTitleIcon.setOnTouchListener(((iconV, event) -> {
+ mSeekBar.dispatchTouchEvent(event);
+ return false;
+ }));
+ }
+ });
}
protected void setUpDeviceIcon(MediaDevice device) {
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
index e352c61..1894bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -20,12 +20,16 @@
import android.view.MotionEvent
import android.view.ViewConfiguration
import com.android.systemui.classifier.Classifier
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.multishade.shared.math.isZero
import com.android.systemui.multishade.shared.model.ProxiedInputModel
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.ShadeController
import javax.inject.Inject
import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
@@ -33,6 +37,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* Encapsulates business logic to handle [MotionEvent]-based user input.
@@ -40,15 +45,31 @@
* This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s
* into the newer multi-shade framework for processing.
*/
+@SysUISingleton
class MultiShadeMotionEventInteractor
@Inject
constructor(
@Application private val applicationContext: Context,
@Application private val applicationScope: CoroutineScope,
private val multiShadeInteractor: MultiShadeInteractor,
+ featureFlags: FeatureFlags,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val falsingManager: FalsingManager,
+ private val shadeController: ShadeController,
) {
+ init {
+ if (featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+ applicationScope.launch {
+ multiShadeInteractor.isAnyShadeExpanded.collect {
+ if (!it && !shadeController.isKeyguard) {
+ shadeController.makeExpandedInvisible()
+ } else {
+ shadeController.makeExpandedVisible(false)
+ }
+ }
+ }
+ }
+ }
private val isAnyShadeExpanded: StateFlow<Boolean> =
multiShadeInteractor.isAnyShadeExpanded.stateIn(
@@ -56,6 +77,7 @@
started = SharingStarted.Eagerly,
initialValue = false,
)
+
private val isBouncerShowing: StateFlow<Boolean> =
keyguardTransitionInteractor
.transitionValue(state = KeyguardState.PRIMARY_BOUNCER)
@@ -102,23 +124,7 @@
false
}
MotionEvent.ACTION_MOVE -> {
- interactionState?.let {
- val pointerIndex = event.findPointerIndex(it.pointerId)
- val currentX = event.getX(pointerIndex)
- val currentY = event.getY(pointerIndex)
- if (!it.isDraggingHorizontally && !it.isDraggingShade) {
- val xDistanceTravelled = currentX - it.initialX
- val yDistanceTravelled = currentY - it.initialY
- val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
- interactionState =
- when {
- yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
- abs(xDistanceTravelled) > touchSlop ->
- it.copy(isDraggingHorizontally = true)
- else -> interactionState
- }
- }
- }
+ onMove(event)
// We want to intercept the rest of the gesture if we're dragging the shade.
isDraggingShade()
@@ -162,7 +168,8 @@
}
true
} else {
- false
+ onMove(event)
+ isDraggingShade()
}
}
?: false
@@ -225,6 +232,32 @@
}
}
+ /**
+ * Handles [MotionEvent.ACTION_MOVE] and sets whether or not we are dragging shade in our
+ * current interaction
+ *
+ * @param event The [MotionEvent] to handle.
+ */
+ private fun onMove(event: MotionEvent) {
+ interactionState?.let {
+ val pointerIndex = event.findPointerIndex(it.pointerId)
+ val currentX = event.getX(pointerIndex)
+ val currentY = event.getY(pointerIndex)
+ if (!it.isDraggingHorizontally && !it.isDraggingShade) {
+ val xDistanceTravelled = currentX - it.initialX
+ val yDistanceTravelled = currentY - it.initialY
+ val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
+ interactionState =
+ when {
+ yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
+ abs(xDistanceTravelled) > touchSlop ->
+ it.copy(isDraggingHorizontally = true)
+ else -> interactionState
+ }
+ }
+ }
+ }
+
private data class InteractionState(
val initialX: Float,
val initialY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index f5c0a94..334c70b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -296,7 +296,8 @@
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
// was used to start the note task.
- putExtra(Intent.EXTRA_USE_STYLUS_MODE, true)
+ val useStylusMode = info.entryPoint != NoteTaskEntryPoint.KEYBOARD_SHORTCUT
+ putExtra(Intent.EXTRA_USE_STYLUS_MODE, useStylusMode)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// We should ensure the note experience can be opened both as a full screen (lockscreen)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index 2fa8f9a..fae325c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -25,7 +25,8 @@
* An entry point represents where the note task has ben called from. In rare cases, it may
* represent a "re-entry" (i.e., [APP_CLIPS]).
*/
-enum class NoteTaskEntryPoint {
+enum class
+NoteTaskEntryPoint {
/** @see [LaunchNoteTaskActivity] */
WIDGET_PICKER_SHORTCUT,
@@ -38,4 +39,7 @@
/** @see [AppClipsTrampolineActivity] */
APP_CLIPS,
+
+ /** @see [NoteTaskInitializer.callbacks] */
+ KEYBOARD_SHORTCUT,
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
index 16dd16e..48a5933 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -18,6 +18,7 @@
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
@@ -51,6 +52,7 @@
WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
APP_CLIPS -> return
+ KEYBOARD_SHORTCUT -> return
null -> return
}
uiEventLogger.log(event, info.uid, info.packageName)
@@ -70,6 +72,7 @@
WIDGET_PICKER_SHORTCUT -> return
QUICK_AFFORDANCE -> return
APP_CLIPS -> return
+ KEYBOARD_SHORTCUT -> return
null -> return
}
uiEventLogger.log(event, info.uid, info.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 04ed08b..23ee13b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -41,9 +41,12 @@
@VisibleForTesting
val callbacks =
object : CommandQueue.Callbacks {
- override fun handleSystemKey(keyCode: Int) {
- if (keyCode == KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL) {
+ override fun handleSystemKey(key: KeyEvent) {
+ if (key.keyCode == KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL) {
controller.showNoteTask(NoteTaskEntryPoint.TAIL_BUTTON)
+ } else if (key.keyCode == KeyEvent.KEYCODE_N && key.isMetaPressed &&
+ key.isCtrlPressed) {
+ controller.showNoteTask(NoteTaskEntryPoint.KEYBOARD_SHORTCUT)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 5355865..0641eec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -47,7 +47,6 @@
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
@@ -80,8 +79,6 @@
/** A controller for the dealing with services running in the foreground. */
interface FgsManagerController {
- /** Whether the TaskManager (and therefore this controller) is actually available. */
- val isAvailable: StateFlow<Boolean>
/** The number of packages with a service running in the foreground. */
val numRunningPackages: Int
@@ -155,7 +152,6 @@
companion object {
private const val INTERACTION_JANK_TAG = "active_background_apps"
- private const val DEFAULT_TASK_MANAGER_ENABLED = true
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = true
@@ -165,9 +161,6 @@
override var newChangesSinceDialogWasDismissed = false
private set
- val _isAvailable = MutableStateFlow(false)
- override val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()
-
val _showFooterDot = MutableStateFlow(false)
override val showFooterDot: StateFlow<Boolean> = _showFooterDot.asStateFlow()
@@ -264,7 +257,6 @@
NAMESPACE_SYSTEMUI,
backgroundExecutor
) {
- _isAvailable.value = it.getBoolean(TASK_MANAGER_ENABLED, _isAvailable.value)
_showFooterDot.value =
it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value)
showStopBtnForUserAllowlistedApps = it.getBoolean(
@@ -280,11 +272,6 @@
TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
informJobSchedulerOfPendingAppStop)
}
-
- _isAvailable.value = deviceConfigProxy.getBoolean(
- NAMESPACE_SYSTEMUI,
- TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED
- )
_showFooterDot.value = deviceConfigProxy.getBoolean(
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 6be74a0..ce690e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -26,6 +26,7 @@
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.util.leak.GarbageMonitor;
@@ -34,7 +35,7 @@
import java.util.Collection;
import java.util.List;
-public interface QSHost extends PanelInteractor {
+public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
String TILES_SETTING = Settings.Secure.QS_TILES;
int POSITION_AT_END = -1;
@@ -102,9 +103,6 @@
void removeTileByUser(ComponentName tile);
void changeTilesByUser(List<String> previousTiles, List<String> newTiles);
- boolean isTileAdded(ComponentName componentName, int userId);
- void setTileAdded(ComponentName componentName, int userId, boolean added);
-
int indexOf(String tileSpec);
InstanceId getNewInstanceId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 958fa71..964fe71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -20,6 +20,8 @@
import com.android.systemui.flags.Flags
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
import dagger.Binds
@@ -46,5 +48,19 @@
qsHost
}
}
+
+ @Provides
+ @JvmStatic
+ fun provideCustomTileAddedRepository(
+ featureFlags: FeatureFlags,
+ qsHost: QSHost,
+ customTileAddedRepository: CustomTileAddedSharedPrefsRepository
+ ): CustomTileAddedRepository {
+ return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ customTileAddedRepository
+ } else {
+ qsHost
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 9f93e49..7a10a27 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -33,6 +33,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.settings.UserTracker;
import java.util.List;
@@ -59,6 +60,7 @@
private final TileLifecycleManager mStateManager;
private final Handler mHandler;
private final UserTracker mUserTracker;
+ private final CustomTileAddedRepository mCustomTileAddedRepository;
private boolean mBindRequested;
private boolean mBindAllowed;
private boolean mBound;
@@ -72,9 +74,10 @@
private boolean mStarted = false;
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
- BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
- this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
- tileServices.getContext(), tileServices,
+ BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
+ CustomTileAddedRepository customTileAddedRepository) {
+ this(tileServices, handler, userTracker, customTileAddedRepository,
+ new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
new Intent(TileService.ACTION_QS_TILE).setComponent(component),
userTracker.getUserHandle()));
@@ -82,11 +85,13 @@
@VisibleForTesting
TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
+ CustomTileAddedRepository customTileAddedRepository,
TileLifecycleManager tileLifecycleManager) {
mServices = tileServices;
mHandler = handler;
mStateManager = tileLifecycleManager;
mUserTracker = userTracker;
+ mCustomTileAddedRepository = customTileAddedRepository;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -111,8 +116,8 @@
mStarted = true;
ComponentName component = mStateManager.getComponent();
final int userId = mStateManager.getUserId();
- if (!mServices.getHost().isTileAdded(component, userId)) {
- mServices.getHost().setTileAdded(component, userId, true);
+ if (!mCustomTileAddedRepository.isTileAdded(component, userId)) {
+ mCustomTileAddedRepository.setTileAdded(component, userId, true);
mStateManager.onTileAdded();
mStateManager.flushMessagesAndUnbind();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 42536fe..121955c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -41,6 +41,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
@@ -77,6 +78,7 @@
private final UserTracker mUserTracker;
private final StatusBarIconController mStatusBarIconController;
private final PanelInteractor mPanelInteractor;
+ private final CustomTileAddedRepository mCustomTileAddedRepository;
private int mMaxBound = DEFAULT_MAX_BOUND;
@@ -89,7 +91,8 @@
KeyguardStateController keyguardStateController,
CommandQueue commandQueue,
StatusBarIconController statusBarIconController,
- PanelInteractor panelInteractor) {
+ PanelInteractor panelInteractor,
+ CustomTileAddedRepository customTileAddedRepository) {
mHost = host;
mKeyguardStateController = keyguardStateController;
mContext = mHost.getContext();
@@ -101,6 +104,7 @@
mStatusBarIconController = statusBarIconController;
mCommandQueue.addCallback(mRequestListeningCallback);
mPanelInteractor = panelInteractor;
+ mCustomTileAddedRepository = customTileAddedRepository;
}
public Context getContext() {
@@ -128,7 +132,7 @@
protected TileServiceManager onCreateTileService(ComponentName component,
BroadcastDispatcher broadcastDispatcher) {
return new TileServiceManager(this, mHandlerProvider.get(), component,
- broadcastDispatcher, mUserTracker);
+ broadcastDispatcher, mUserTracker, mCustomTileAddedRepository);
}
public void freeService(CustomTile tile, TileServiceManager service) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
index 37a9c40..bd9d70c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
@@ -32,8 +32,6 @@
interface ForegroundServicesRepository {
/**
* The number of packages with a service running in the foreground.
- *
- * Note that this will be equal to 0 if [FgsManagerController.isAvailable] is false.
*/
val foregroundServicesCount: Flow<Int>
@@ -52,32 +50,24 @@
fgsManagerController: FgsManagerController,
) : ForegroundServicesRepository {
override val foregroundServicesCount: Flow<Int> =
- fgsManagerController.isAvailable
- .flatMapLatest { isAvailable ->
- if (!isAvailable) {
- return@flatMapLatest flowOf(0)
+ conflatedCallbackFlow<Int> {
+ fun updateState(numberOfPackages: Int) {
+ trySendWithFailureLogging(numberOfPackages, TAG)
}
- conflatedCallbackFlow {
- fun updateState(numberOfPackages: Int) {
- trySendWithFailureLogging(numberOfPackages, TAG)
- }
-
- val listener =
+ val listener =
object : FgsManagerController.OnNumberOfPackagesChangedListener {
override fun onNumberOfPackagesChanged(numberOfPackages: Int) {
updateState(numberOfPackages)
}
}
- fgsManagerController.addOnNumberOfPackagesChangedListener(listener)
- updateState(fgsManagerController.numRunningPackages)
- awaitClose {
- fgsManagerController.removeOnNumberOfPackagesChangedListener(listener)
- }
+ fgsManagerController.addOnNumberOfPackagesChangedListener(listener)
+ updateState(fgsManagerController.numRunningPackages)
+ awaitClose {
+ fgsManagerController.removeOnNumberOfPackagesChangedListener(listener)
}
- }
- .distinctUntilChanged()
+ }.distinctUntilChanged()
override val hasNewChanges: Flow<Boolean> =
fgsManagerController.showFooterDot.flatMapLatest { showFooterDot ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedRepository.kt
new file mode 100644
index 0000000..7fc906b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserFileManager
+import javax.inject.Inject
+
+/**
+ * Repository for keeping track of whether a given [CustomTile] [ComponentName] has been added to
+ * the set of current tiles for a user. This is used to determine when lifecycle methods in
+ * `TileService` about the tile being added/removed need to be called.
+ */
+interface CustomTileAddedRepository {
+ /**
+ * Check if a particular [CustomTile] associated with [componentName] has been added for
+ * [userId] and has not been removed since.
+ */
+ fun isTileAdded(componentName: ComponentName, userId: Int): Boolean
+
+ /**
+ * Persists whether a particular [CustomTile] associated with [componentName] has been added and
+ * it's currently in the set of selected tiles for [userId].
+ */
+ fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean)
+}
+
+@SysUISingleton
+class CustomTileAddedSharedPrefsRepository
+@Inject
+constructor(private val userFileManager: UserFileManager) : CustomTileAddedRepository {
+
+ override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean {
+ return userFileManager
+ .getSharedPreferences(TILES, 0, userId)
+ .getBoolean(componentName.flattenToString(), false)
+ }
+
+ override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) {
+ userFileManager
+ .getSharedPreferences(TILES, 0, userId)
+ .edit()
+ .putBoolean(componentName.flattenToString(), added)
+ .apply()
+ }
+
+ companion object {
+ private const val TILES = "tiles_prefs"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 3090b79..4a31998 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -496,7 +496,7 @@
}
// Colors
- if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
+ if (state.state != lastState || state.disabledByPolicy != lastDisabledByPolicy) {
singleAnimator.cancel()
mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
state.spec,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 1b83397..f62e939 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -28,15 +28,15 @@
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_ON;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_TRANSITION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
import android.annotation.FloatRange;
import android.app.ActivityTaskManager;
@@ -85,6 +85,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -336,7 +337,8 @@
@Override
public void expandNotificationPanel() {
verifyCallerAndClearCallingIdentity("expandNotificationPanel",
- () -> mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
+ () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
}
@Override
@@ -520,6 +522,7 @@
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
UserTracker userTracker,
ScreenLifecycle screenLifecycle,
+ WakefulnessLifecycle wakefulnessLifecycle,
UiEventLogger uiEventLogger,
DisplayTracker displayTracker,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -597,8 +600,8 @@
// Listen for user setup
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
- screenLifecycle.addObserver(mLifecycleObserver);
-
+ screenLifecycle.addObserver(mScreenLifecycleObserver);
+ wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver);
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
@@ -863,81 +866,94 @@
}
}
- private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
- /**
- * Notifies the Launcher that screen turned on and ready to use
- */
- @Override
- public void onScreenTurnedOn() {
- mSysUiState
- .setFlag(SYSUI_STATE_SCREEN_ON, true)
- .setFlag(SYSUI_STATE_SCREEN_TRANSITION, false)
- .commitUpdate(mContext.getDisplayId());
-
- try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onScreenTurnedOn();
- } else {
- Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event.");
+ private final ScreenLifecycle.Observer mScreenLifecycleObserver =
+ new ScreenLifecycle.Observer() {
+ /**
+ * Notifies the Launcher that screen turned on and ready to use
+ */
+ @Override
+ public void onScreenTurnedOn() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurnedOn();
+ } else {
+ Log.e(TAG_OPS,
+ "Failed to get overview proxy for screen turned on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);
+ }
}
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);
- }
- }
- /**
- * Notifies the Launcher that screen turned off.
- */
- @Override
- public void onScreenTurnedOff() {
- mSysUiState
- .setFlag(SYSUI_STATE_SCREEN_ON, false)
- .setFlag(SYSUI_STATE_SCREEN_TRANSITION, false)
- .commitUpdate(mContext.getDisplayId());
- }
-
- /**
- * Notifies the Launcher that screen is starting to turn on.
- */
- @Override
- public void onScreenTurningOff() {
- mSysUiState
- .setFlag(SYSUI_STATE_SCREEN_ON, false)
- .setFlag(SYSUI_STATE_SCREEN_TRANSITION, true)
- .commitUpdate(mContext.getDisplayId());
-
- try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onScreenTurningOff();
- } else {
- Log.e(TAG_OPS, "Failed to get overview proxy for screen turning off event.");
+ /**
+ * Notifies the Launcher that screen is starting to turn on.
+ */
+ @Override
+ public void onScreenTurningOff() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurningOff();
+ } else {
+ Log.e(TAG_OPS,
+ "Failed to get overview proxy for screen turning off event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e);
+ }
}
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e);
- }
- }
- /**
- * Notifies the Launcher that screen is starting to turn on.
- */
- @Override
- public void onScreenTurningOn() {
- mSysUiState
- .setFlag(SYSUI_STATE_SCREEN_ON, true)
- .setFlag(SYSUI_STATE_SCREEN_TRANSITION, true)
- .commitUpdate(mContext.getDisplayId());
-
- try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onScreenTurningOn();
- } else {
- Log.e(TAG_OPS, "Failed to get overview proxy for screen turning on event.");
+ /**
+ * Notifies the Launcher that screen is starting to turn on.
+ */
+ @Override
+ public void onScreenTurningOn() {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onScreenTurningOn();
+ } else {
+ Log.e(TAG_OPS,
+ "Failed to get overview proxy for screen turning on event.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e);
+ }
}
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e);
- }
- }
- };
+ };
+
+ private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
+ new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onStartedWakingUp() {
+ mSysUiState
+ .setFlag(SYSUI_STATE_AWAKE, true)
+ .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
+ @Override
+ public void onFinishedWakingUp() {
+ mSysUiState
+ .setFlag(SYSUI_STATE_AWAKE, true)
+ .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
+ @Override
+ public void onStartedGoingToSleep() {
+ mSysUiState
+ .setFlag(SYSUI_STATE_AWAKE, false)
+ .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, true)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
+ @Override
+ public void onFinishedGoingToSleep() {
+ mSysUiState
+ .setFlag(SYSUI_STATE_AWAKE, false)
+ .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false)
+ .commitUpdate(mContext.getDisplayId());
+ }
+ };
void notifyToggleRecentApps() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index a9af1a2..6f85c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -44,7 +44,6 @@
import android.app.Notification;
import android.app.assist.AssistContent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -525,39 +524,6 @@
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
}
- @MainThread
- void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
- RequestCallback requestCallback) {
- Assert.isMainThread();
- mCurrentRequestCallback = requestCallback;
- takeScreenshotInternal(topComponent, finisher, getFullScreenRect());
- }
-
- @MainThread
- void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
- Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
- Consumer<Uri> finisher, RequestCallback requestCallback) {
- Assert.isMainThread();
- if (screenshot == null) {
- Log.e(TAG, "Got null bitmap from screenshot message");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- requestCallback.reportError();
- return;
- }
-
- boolean showFlash = false;
- if (screenshotScreenBounds == null
- || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
- showFlash = true;
- visibleInsets = Insets.NONE;
- screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
- }
- mCurrentRequestCallback = requestCallback;
- saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
- showFlash, UserHandle.of(userId));
- }
-
/**
* Clears current screenshot
*/
@@ -695,103 +661,6 @@
setContentView(mScreenshotView);
}
- /**
- * Takes a screenshot of the current display and shows an animation.
- */
- private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher,
- Rect crop) {
- mScreenshotTakenInPortrait =
- mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
-
- // copy the input Rect, since SurfaceControl.screenshot can mutate it
- Rect screenRect = new Rect(crop);
- Bitmap screenshot = mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(),
- crop);
-
- if (screenshot == null) {
- Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- if (mCurrentRequestCallback != null) {
- mCurrentRequestCallback.reportError();
- }
- return;
- }
-
- saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true,
- Process.myUserHandle());
-
- mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
- ClipboardOverlayController.SELF_PERMISSION);
- }
-
- private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
- Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
- withWindowAttached(() -> {
- if (mUserManager.isManagedProfile(owner.getIdentifier())) {
- mScreenshotView.announceForAccessibility(mContext.getResources().getString(
- R.string.screenshot_saving_work_profile_title));
- } else {
- mScreenshotView.announceForAccessibility(
- mContext.getResources().getString(R.string.screenshot_saving_title));
- }
- });
-
- mScreenshotView.reset();
-
- if (mScreenshotView.isAttachedToWindow()) {
- // if we didn't already dismiss for another reason
- if (!mScreenshotView.isDismissing()) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, mPackageName);
- }
- if (DEBUG_WINDOW) {
- Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
- + "(dismissing=" + mScreenshotView.isDismissing() + ")");
- }
- }
- mPackageName = topComponent == null ? "" : topComponent.getPackageName();
- mScreenshotView.setPackageName(mPackageName);
-
- mScreenshotView.updateOrientation(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
-
- mScreenBitmap = screenshot;
-
- if (!isUserSetupComplete(owner)) {
- Log.w(TAG, "User setup not complete, displaying toast only");
- // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
- // and sharing shouldn't be exposed to the user.
- saveScreenshotAndToast(owner, finisher);
- return;
- }
-
- // Optimizations
- mScreenBitmap.setHasAlpha(false);
- mScreenBitmap.prepareToDraw();
-
- saveScreenshotInWorkerThread(owner, finisher, this::showUiOnActionsReady,
- this::showUiOnQuickShareActionReady);
-
- // The window is focusable by default
- setWindowFocusable(true);
- mScreenshotView.requestFocus();
-
- enqueueScrollCaptureRequest(owner);
-
- attachWindow();
- prepareAnimation(screenRect, showFlash, () -> {
- mMessageContainerController.onScreenshotTaken(owner);
- });
-
- mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
- mContext.getDrawable(R.drawable.overlay_badge_background), owner));
- mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
- // ignore system bar insets for the purpose of window layout
- mWindow.getDecorView().setOnApplyWindowInsetsListener(
- (v, insets) -> WindowInsets.CONSUMED);
- mScreenshotHandler.cancelTimeout(); // restarted after animation
- }
-
private void prepareAnimation(Rect screenRect, boolean showFlash,
Runnable onAnimationComplete) {
mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f3d2828..1cdad83 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -36,9 +36,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
@@ -49,7 +46,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
-import android.view.WindowManager;
import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
@@ -58,7 +54,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -222,30 +217,17 @@
return;
}
- if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA_REFACTOR)) {
- Log.d(TAG, "Processing screenshot data");
- ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
- try {
- mProcessor.processAsync(screenshotData,
- (data) -> dispatchToController(data, onSaved, callback));
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to process screenshot request!", e);
- logFailedRequest(request);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- }
- } else {
- try {
- mProcessor.processAsync(request,
- (r) -> dispatchToController(r, onSaved, callback));
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to process screenshot request!", e);
- logFailedRequest(request);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- callback.reportError();
- }
+ Log.d(TAG, "Processing screenshot data");
+ ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
+ try {
+ mProcessor.processAsync(screenshotData,
+ (data) -> dispatchToController(data, onSaved, callback));
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to process screenshot request!", e);
+ logFailedRequest(request);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ callback.reportError();
}
}
@@ -257,38 +239,6 @@
mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
}
- private void dispatchToController(ScreenshotRequest request,
- Consumer<Uri> uriConsumer, RequestCallback callback) {
- ComponentName topComponent = request.getTopComponent();
- String packageName = topComponent == null ? "" : topComponent.getPackageName();
- mUiEventLogger.log(
- ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
-
- switch (request.getType()) {
- case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
- if (DEBUG_SERVICE) {
- Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
- }
- mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
- break;
- case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
- if (DEBUG_SERVICE) {
- Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
- }
- Bitmap screenshot = request.getBitmap();
- Rect screenBounds = request.getBoundsInScreen();
- Insets insets = request.getInsets();
- int taskId = request.getTaskId();
- int userId = request.getUserId();
-
- mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
- taskId, userId, topComponent, uriConsumer, callback);
- break;
- default:
- Log.wtf(TAG, "Invalid screenshot option: " + request.getType());
- }
- }
-
private void logFailedRequest(ScreenshotRequest request) {
ComponentName topComponent = request.getTopComponent();
String packageName = topComponent == null ? "" : topComponent.getPackageName();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7b46852..b7243ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -18,6 +18,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
+import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -157,6 +158,7 @@
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -310,7 +312,7 @@
*/
public final boolean mAnimateBack;
- private final boolean mTrackpadGestureBack;
+ private final boolean mTrackpadGestureFeaturesEnabled;
/**
* The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
*/
@@ -391,6 +393,7 @@
private KeyguardBottomAreaView mKeyguardBottomArea;
private boolean mExpanding;
private boolean mSplitShadeEnabled;
+ private boolean mDualShadeEnabled;
/** The bottom padding reserved for elements of the keyguard measuring notifications. */
private float mKeyguardNotificationBottomPadding;
/**
@@ -622,7 +625,9 @@
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
+ private final @Nullable MultiShadeInteractor mMultiShadeInteractor;
private final CoroutineDispatcher mMainDispatcher;
+ private boolean mIsAnyMultiShadeExpanded;
private boolean mIsOcclusionTransitionRunning = false;
private int mDreamingToLockscreenTransitionTranslationY;
private int mOccludedToLockscreenTransitionTranslationY;
@@ -644,6 +649,9 @@
}
};
+ private final Consumer<Boolean> mMultiShadeExpansionConsumer =
+ (Boolean expanded) -> mIsAnyMultiShadeExpanded = expanded;
+
private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
(TransitionStep step) -> {
mIsOcclusionTransitionRunning =
@@ -760,6 +768,7 @@
LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ Provider<MultiShadeInteractor> multiShadeInteractorProvider,
DumpManager dumpManager,
KeyguardLongPressViewModel keyguardLongPressViewModel,
KeyguardInteractor keyguardInteractor) {
@@ -859,7 +868,9 @@
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
- mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
+ mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
+ mDualShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
+ mMultiShadeInteractor = mDualShadeEnabled ? multiShadeInteractorProvider.get() : null;
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -1096,6 +1107,11 @@
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
+ if (mDualShadeEnabled) {
+ collectFlow(mView, mMultiShadeInteractor.isAnyShadeExpanded(),
+ mMultiShadeExpansionConsumer, mMainDispatcher);
+ }
+
// Dreaming->Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
mDreamingToLockscreenTransition, mMainDispatcher);
@@ -4616,7 +4632,9 @@
mQsController.setExpandImmediate(false);
// Close the status bar in the next frame so we can show the end of the
// animation.
- mView.post(mMaybeHideExpandedRunnable);
+ if (!mIsAnyMultiShadeExpanded) {
+ mView.post(mMaybeHideExpandedRunnable);
+ }
}
mCurrentPanelState = state;
}
@@ -4937,7 +4955,8 @@
}
// On expanding, single mouse click expands the panel instead of dragging.
- if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE)
+ && !isTrackpadMotionEvent(event))) {
if (event.getAction() == MotionEvent.ACTION_UP) {
expand(true /* animate */);
}
@@ -5092,8 +5111,9 @@
}
private boolean isTrackpadMotionEvent(MotionEvent ev) {
- return mTrackpadGestureBack
- && ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+ return mTrackpadGestureFeaturesEnabled && (
+ ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ || ev.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index ad5a68e4d..e08bc33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -64,6 +64,11 @@
boolean closeShadeIfOpen();
/**
+ * Returns whether the shade state is the keyguard or not.
+ */
+ boolean isKeyguard();
+
+ /**
* Returns whether the shade is currently open.
* Even though in the current implementation shade is in expanded state on keyguard, this
* method makes distinction between shade being truly open and plain keyguard state:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 826b3ee..c71467b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -154,6 +154,11 @@
}
@Override
+ public boolean isKeyguard() {
+ return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ }
+
+ @Override
public boolean isShadeFullyOpen() {
return mNotificationPanelViewController.isShadeFullyExpanded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c435799..fb4feb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,6 +53,7 @@
import android.os.RemoteException;
import android.util.Pair;
import android.util.SparseArray;
+import android.view.KeyEvent;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -302,7 +303,7 @@
default void remQsTile(ComponentName tile) { }
default void clickTile(ComponentName tile) { }
- default void handleSystemKey(int arg1) { }
+ default void handleSystemKey(KeyEvent arg1) { }
default void showPinningEnterExitToast(boolean entering) { }
default void showPinningEscapeToast() { }
default void handleShowGlobalActionsMenu() { }
@@ -891,9 +892,9 @@
}
@Override
- public void handleSystemKey(int key) {
+ public void handleSystemKey(KeyEvent key) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_HANDLE_SYSTEM_KEY, key, 0).sendToTarget();
+ mHandler.obtainMessage(MSG_HANDLE_SYSTEM_KEY, key).sendToTarget();
}
}
@@ -1534,7 +1535,7 @@
break;
case MSG_HANDLE_SYSTEM_KEY:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).handleSystemKey(msg.arg1);
+ mCallbacks.get(i).handleSystemKey((KeyEvent) msg.obj);
}
break;
case MSG_SHOW_GLOBAL_ACTIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index fda2277..765c93e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -869,12 +869,9 @@
// Walk down a precedence-ordered list of what indication
// should be shown based on device state
if (mDozing) {
+ boolean useMisalignmentColor = false;
mLockScreenIndicationView.setVisibility(View.GONE);
mTopIndicationView.setVisibility(VISIBLE);
- // When dozing we ignore any text color and use white instead, because
- // colors can be hard to read in low brightness.
- mTopIndicationView.setTextColor(Color.WHITE);
-
CharSequence newIndication;
if (!TextUtils.isEmpty(mBiometricMessage)) {
newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
@@ -885,8 +882,8 @@
mIndicationArea.setVisibility(GONE);
return;
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+ useMisalignmentColor = true;
newIndication = mAlignmentIndication;
- mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
newIndication = computePowerIndication();
} else {
@@ -896,7 +893,14 @@
if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
mWakeLock.setAcquired(true);
- mTopIndicationView.switchIndication(newIndication, null,
+ mTopIndicationView.switchIndication(newIndication,
+ new KeyguardIndication.Builder()
+ .setMessage(newIndication)
+ .setTextColor(ColorStateList.valueOf(
+ useMisalignmentColor
+ ? mContext.getColor(R.color.misalignment_text_color)
+ : Color.WHITE))
+ .build(),
true, () -> mWakeLock.setAcquired(false));
}
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 9272f06..0195d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -157,7 +157,8 @@
if (animationAdapter != null) {
if (ENABLE_SHELL_TRANSITIONS) {
options = ActivityOptions.makeRemoteTransition(
- RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter));
+ RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter,
+ "SysUILaunch"));
} else {
options = ActivityOptions.makeRemoteAnimation(animationAdapter);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e776015..8b6617b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -308,7 +308,7 @@
* settings. Down action closes the entire panel.
*/
@Override
- public void handleSystemKey(int key) {
+ public void handleSystemKey(KeyEvent key) {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG, "handleNavigationKey: " + key);
}
@@ -320,11 +320,11 @@
// Panels are not available in setup
if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
- if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
+ if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key.getKeyCode()) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
mNotificationPanelViewController.collapse(
false /* delayed */, 1.0f /* speedUpFactor */);
- } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
+ } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
if (mNotificationPanelViewController.isFullyCollapsed()) {
if (mVibrateOnOpening) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index c817466..b303151 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -85,16 +85,17 @@
/**
* Called when keyguard is about to be displayed and allows to perform custom animation
+ *
+ * @return A handle that can be used for cancelling the animation, if necessary
*/
- fun animateInKeyguard(keyguardView: View, after: Runnable) =
- animations.firstOrNull {
+ fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
+ animations.forEach {
if (it.shouldAnimateInKeyguard()) {
- it.animateInKeyguard(keyguardView, after)
- true
- } else {
- false
+ return@animateInKeyguard it.animateInKeyguard(keyguardView, after)
}
}
+ return null
+ }
/**
* If returns true it will disable propagating touches to apps and keyguard
@@ -211,7 +212,10 @@
fun onAlwaysOnChanged(alwaysOn: Boolean) {}
fun shouldAnimateInKeyguard(): Boolean = false
- fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
+ fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
+ after.run()
+ return null
+ }
fun shouldDelayKeyguardShow(): Boolean = false
fun isKeyguardShowDelayed(): Boolean = false
@@ -224,3 +228,7 @@
fun shouldAnimateDozingChange(): Boolean = true
fun shouldAnimateClockChange(): Boolean = true
}
+
+interface AnimatorHandle {
+ fun cancel()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 118bfc5..deb0414 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -160,7 +160,7 @@
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
*/
- override fun animateInKeyguard(keyguardView: View, after: Runnable) {
+ override fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle {
shouldAnimateInKeyguard = false
keyguardView.alpha = 0f
keyguardView.visibility = View.VISIBLE
@@ -175,11 +175,36 @@
// We animate the Y properly separately using the PropertyAnimator, as the panel
// view also needs to update the end position.
PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
- PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY,
- AnimationProperties().setDuration(duration.toLong()),
- true /* animate */)
- keyguardView.animate()
+ // Start the animation on the next frame using Choreographer APIs. animateInKeyguard() is
+ // called while the system is busy processing lots of requests, so delaying the animation a
+ // frame will mitigate jank. In the event the animation is cancelled before the next frame
+ // is called, this callback will be removed
+ val keyguardAnimator = keyguardView.animate()
+ val nextFrameCallback = TraceUtils.namedRunnable("startAnimateInKeyguard") {
+ PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
+ AnimationProperties().setDuration(duration.toLong()),
+ true /* animate */)
+ keyguardAnimator.start()
+ }
+ DejankUtils.postAfterTraversal(nextFrameCallback)
+ val animatorHandle = object : AnimatorHandle {
+ private var hasCancelled = false
+ override fun cancel() {
+ if (!hasCancelled) {
+ DejankUtils.removeCallbacks(nextFrameCallback)
+ // If we're cancelled, reset state flags/listeners. The end action above
+ // will not be called, which is what we want since that will finish the
+ // screen off animation and show the lockscreen, which we don't want if we
+ // were cancelled.
+ aodUiAnimationPlaying = false
+ decidedToAnimateGoingToSleep = null
+ keyguardView.animate().setListener(null)
+ hasCancelled = true
+ }
+ }
+ }
+ keyguardAnimator
.setDuration(duration.toLong())
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
@@ -205,14 +230,7 @@
}
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
- // If we're cancelled, reset state flags/listeners. The end action above
- // will not be called, which is what we want since that will finish the
- // screen off animation and show the lockscreen, which we don't want if we
- // were cancelled.
- aodUiAnimationPlaying = false
- decidedToAnimateGoingToSleep = null
- keyguardView.animate().setListener(null)
-
+ animatorHandle.cancel()
interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
}
@@ -222,7 +240,7 @@
CUJ_SCREEN_OFF_SHOW_AOD)
}
})
- .start()
+ return animatorHandle
}
override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index bd60401..e492534 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -295,8 +295,8 @@
@Override
public void notifyExpandNotification() {
mSysUiMainExecutor.execute(
- () -> mCommandQueue.handleSystemKey(
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
+ () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
}
});
diff --git a/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
new file mode 100644
index 0000000..68473ba
--- /dev/null
+++ b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg
Binary files differ
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 7ce2b1c..1ba9931 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -152,19 +152,16 @@
false);
}
+
@Test
public void testReset() {
mKeyguardAbsKeyInputViewController.reset();
verify(mKeyguardMessageAreaController).setMessage("", false);
- verify(mAbsKeyInputView).resetPasswordText(false, false);
- verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
}
@Test
- public void onResume_Reset() {
+ public void testResume() {
mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
- verify(mKeyguardMessageAreaController).setMessage("", false);
- verify(mAbsKeyInputView).resetPasswordText(false, false);
verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 6ae28b7..a8d5569 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -122,22 +122,8 @@
}
@Test
- fun reset() {
- mKeyguardPatternViewController.reset()
- verify(mLockPatternView).setInStealthMode(anyBoolean())
- verify(mLockPatternView).enableInput()
- verify(mLockPatternView).setEnabled(true)
- verify(mLockPatternView).clearPattern()
- verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
- }
-
- @Test
fun resume() {
mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(mLockPatternView).setInStealthMode(anyBoolean())
- verify(mLockPatternView).enableInput()
- verify(mLockPatternView).setEnabled(true)
- verify(mLockPatternView).clearPattern()
verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 33f0ae5..b6287598 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -141,27 +141,6 @@
}
@Test
- public void testUnlockIconShows_biometricUnlockedTrue() {
- // GIVEN UDFPS sensor location is available
- setupUdfps();
-
- // GIVEN lock icon controller is initialized and view is attached
- init(/* useMigrationFlag= */false);
- captureKeyguardUpdateMonitorCallback();
-
- // GIVEN user has unlocked with a biometric auth (ie: face auth)
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
- reset(mLockIconView);
-
- // WHEN face auth's biometric running state changes
- mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
- BiometricSourceType.FACE);
-
- // THEN the unlock icon is shown
- verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
- }
-
- @Test
public void testLockIconStartState() {
// GIVEN lock icon state
setupShowLockIcon();
@@ -268,27 +247,6 @@
}
@Test
- public void lockIconShows_afterBiometricsCleared() {
- // GIVEN lock icon controller is initialized and view is attached
- init(/* useMigrationFlag= */false);
- captureKeyguardUpdateMonitorCallback();
-
- // GIVEN user has unlocked with a biometric auth (ie: face auth)
- // and biometric running state changes
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
- mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
- BiometricSourceType.FACE);
- reset(mLockIconView);
-
- // WHEN biometrics are cleared
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
- mKeyguardUpdateMonitorCallback.onBiometricsCleared();
-
- // THEN the lock icon is shown
- verify(mLockIconView).setContentDescription(LOCKED_LABEL);
- }
-
- @Test
public void lockIconShows_afterUnlockStateChanges() {
// GIVEN lock icon controller is initialized and view is attached
init(/* useMigrationFlag= */false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index bb037642..b2c2ae7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -21,6 +21,7 @@
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
@@ -29,6 +30,7 @@
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
+import android.view.Surface.ROTATION_0
import android.view.Surface.Rotation
import android.view.View
import android.view.WindowManager
@@ -42,6 +44,7 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -159,9 +162,10 @@
private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
// Sensor that's in the top left corner of the display in natural orientation.
val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
+ val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
overlayParams = UdfpsOverlayParams(
sensorBounds,
- sensorBounds,
+ overlayBounds,
DISPLAY_WIDTH,
DISPLAY_HEIGHT,
scaleFactor = 1f,
@@ -314,4 +318,24 @@
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
}
+
+ @Test
+ fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) {
+ withReason(REASON_ENROLL_ENROLLING) {
+ // When a11y enabled during enrollment
+ whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)
+ whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
+
+ controllerOverlay.show(udfpsController, overlayParams)
+ verify(windowManager).addView(
+ eq(controllerOverlay.overlayView),
+ layoutParamsCaptor.capture()
+ )
+
+ // Layout params should use sensor bounds
+ val lp = layoutParamsCaptor.value
+ assertThat(lp.width).isEqualTo(overlayParams.sensorBounds.width())
+ assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
+ }
+ }
}
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 445cc87..edee3f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1344,6 +1344,46 @@
}
@Test
+ public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing()
+ throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultUnchanged =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
+ 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // Configure UdfpsView to not accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
+
+ // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received and touch is not within sensor
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultUnchanged);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricExecutor.runAllReady();
+ downEvent.recycle();
+
+ // THEN the touch is pilfered
+ verify(mInputManager, times(1)).pilferPointers(any());
+ }
+
+ @Test
public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException {
// GIVEN UDFPS overlay is showing
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index f437a8f..6d9acb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
@@ -157,6 +158,22 @@
}
@Test
+ public void onBiometricAuthenticated_pauseAuth() {
+ // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ // WHEN biometric is authenticated
+ captureKeyguardStateControllerCallback();
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardStateControllerCallback.onUnlockedChanged();
+
+ // THEN pause auth
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
// GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
mController.onViewAttached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
index 236384b..4ea9616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt
@@ -32,6 +32,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -161,6 +162,7 @@
}
verify(controlsModelCallback).onFirstChange()
+ verify(controlsModelCallback).onChange()
}
@Test
@@ -176,6 +178,7 @@
)
verify(controlsModelCallback).onFirstChange()
+ verify(controlsModelCallback).onChange()
}
@Test
@@ -191,6 +194,7 @@
}
verify(controlsModelCallback, never()).onFirstChange()
+ verify(controlsModelCallback, never()).onChange()
}
@Test
@@ -207,6 +211,7 @@
}
verify(controlsModelCallback).onFirstChange()
+ verify(controlsModelCallback).onChange()
}
@Test
@@ -222,6 +227,7 @@
)
verify(controlsModelCallback).onFirstChange()
+ verify(controlsModelCallback).onChange()
}
@Test
@@ -236,5 +242,24 @@
}
verify(controlsModelCallback, never()).onFirstChange()
+ verify(controlsModelCallback, never()).onChange()
+ }
+
+ @Test
+ fun testAddSecondChange_callbacks() {
+ model.changeFavoriteStatus("${idPrefix}4", true)
+ model.changeFavoriteStatus("${idPrefix}5", true)
+
+ verify(controlsModelCallback).onFirstChange()
+ verify(controlsModelCallback, times(2)).onChange()
+ }
+
+ @Test
+ fun testRemoveSecondChange_callbacks() {
+ model.changeFavoriteStatus("${idPrefix}1", false)
+ model.changeFavoriteStatus("${idPrefix}3", false)
+
+ verify(controlsModelCallback).onFirstChange()
+ verify(controlsModelCallback, times(2)).onChange()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 3b6f7d1..4210675 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -2,27 +2,33 @@
import android.content.ComponentName
import android.content.Intent
+import android.os.Bundle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
+import android.widget.Button
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -32,7 +38,15 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsEditingActivityTest : SysuiTestCase() {
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName")
+ val TEST_STRUCTURE: CharSequence = "TestStructure"
+ val TEST_APP: CharSequence = "TestApp"
+ }
+
private val uiExecutor = FakeExecutor(FakeSystemClock())
+ private val featureFlags = FakeFeatureFlags()
@Mock lateinit var controller: ControlsControllerImpl
@@ -40,9 +54,6 @@
@Mock lateinit var customIconCache: CustomIconCache
- @Mock lateinit var uiController: ControlsUiController
-
- private lateinit var controlsEditingActivity: ControlsEditingActivity_Factory
private var latch: CountDownLatch = CountDownLatch(1)
@Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
@@ -58,11 +69,11 @@
) {
override fun create(intent: Intent?): TestableControlsEditingActivity {
return TestableControlsEditingActivity(
+ featureFlags,
uiExecutor,
controller,
userTracker,
customIconCache,
- uiController,
mockDispatcher,
latch
)
@@ -75,19 +86,17 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- val intent = Intent()
- intent.putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, "TestTitle")
- val cname = ComponentName("TestPackageName", "TestClassName")
- intent.putExtra(Intent.EXTRA_COMPONENT_NAME, cname)
- activityRule.launchActivity(intent)
+
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
}
@Test
fun testBackCallbackRegistrationAndUnregistration() {
+ launchActivity()
// 1. ensure that launching the activity results in it registering a callback
verify(mockDispatcher)
.registerOnBackInvokedCallback(
- ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
captureCallback.capture()
)
activityRule.finishActivity()
@@ -96,15 +105,102 @@
verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
}
- public class TestableControlsEditingActivity(
- private val executor: FakeExecutor,
- private val controller: ControlsControllerImpl,
- private val userTracker: UserTracker,
- private val customIconCache: CustomIconCache,
- private val uiController: ControlsUiController,
+ @Test
+ fun testNewFlowDisabled_addControlsButton_gone() {
+ with(launchActivity()) {
+ val addControlsButton = requireViewById<Button>(R.id.addControls)
+ assertThat(addControlsButton.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun testNewFlowEnabled_addControlsButton_visible() {
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ with(launchActivity()) {
+ val addControlsButton = requireViewById<Button>(R.id.addControls)
+ assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE)
+ assertThat(addControlsButton.isEnabled).isTrue()
+ }
+ }
+
+ @Test
+ fun testNotLaunchFromFavoriting_saveButton_disabled() {
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ with(launchActivity(isFromFavoriting = false)) {
+ val saveButton = requireViewById<Button>(R.id.done)
+ assertThat(saveButton.isEnabled).isFalse()
+ }
+ }
+
+ @Test
+ fun testLaunchFromFavoriting_saveButton_enabled() {
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ with(launchActivity(isFromFavoriting = true)) {
+ val saveButton = requireViewById<Button>(R.id.done)
+ assertThat(saveButton.isEnabled).isTrue()
+ }
+ }
+
+ @Test
+ fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() {
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ with(launchActivity(isFromFavoriting = false)) {
+ val addControls = requireViewById<Button>(R.id.addControls)
+
+ activityRule.runOnUiThread { addControls.performClick() }
+
+ with(startActivityData!!.intent) {
+ assertThat(component)
+ .isEqualTo(ComponentName(context, ControlsFavoritingActivity::class.java))
+ assertThat(getCharSequenceExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE))
+ .isEqualTo(TEST_STRUCTURE)
+ assertThat(
+ getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+ )
+ .isEqualTo(TEST_COMPONENT)
+ assertThat(getCharSequenceExtra(ControlsFavoritingActivity.EXTRA_APP))
+ .isEqualTo(TEST_APP)
+ assertThat(getByteExtra(ControlsFavoritingActivity.EXTRA_SOURCE, -1))
+ .isEqualTo(ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING)
+ }
+ }
+ }
+
+ private fun launchActivity(
+ componentName: ComponentName = TEST_COMPONENT,
+ structure: CharSequence = TEST_STRUCTURE,
+ isFromFavoriting: Boolean = false,
+ app: CharSequence = TEST_APP,
+ ): TestableControlsEditingActivity =
+ activityRule.launchActivity(
+ Intent().apply {
+ putExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, isFromFavoriting)
+ putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, structure)
+ putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
+ putExtra(ControlsEditingActivity.EXTRA_APP, app)
+ }
+ )
+
+ class TestableControlsEditingActivity(
+ featureFlags: FakeFeatureFlags,
+ executor: FakeExecutor,
+ controller: ControlsControllerImpl,
+ userTracker: UserTracker,
+ customIconCache: CustomIconCache,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
- ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
+ ) :
+ ControlsEditingActivity(
+ featureFlags,
+ executor,
+ controller,
+ userTracker,
+ customIconCache,
+ ) {
+
+ var startActivityData: StartActivityData? = null
+ private set
+
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
return mockDispatcher
}
@@ -114,5 +210,13 @@
// ensures that test runner thread does not proceed until ui thread is done
latch.countDown()
}
+
+ override fun startActivity(intent: Intent) {
+ startActivityData = StartActivityData(intent, null)
+ }
+
+ override fun startActivity(intent: Intent, options: Bundle?) {
+ startActivityData = StartActivityData(intent, options)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 3655232..6884616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -1,30 +1,49 @@
package com.android.systemui.controls.management
+import android.content.ComponentName
import android.content.Intent
+import android.os.Bundle
+import android.service.controls.Control
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
+import android.widget.Button
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.controller.createLoadDataObject
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Answers
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -32,7 +51,19 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsFavoritingActivityTest : SysuiTestCase() {
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName")
+ val TEST_CONTROL =
+ mock(Control::class.java, Answers.RETURNS_MOCKS)!!.apply {
+ whenever(structure).thenReturn(TEST_STRUCTURE)
+ }
+ val TEST_STRUCTURE: CharSequence = "TestStructure"
+ val TEST_APP: CharSequence = "TestApp"
+ }
+
@Main private val executor: Executor = MoreExecutors.directExecutor()
+ private val featureFlags = FakeFeatureFlags()
@Mock lateinit var controller: ControlsControllerImpl
@@ -40,13 +71,15 @@
@Mock lateinit var userTracker: UserTracker
- @Mock lateinit var uiController: ControlsUiController
-
- private lateinit var controlsFavoritingActivity: ControlsFavoritingActivity_Factory
private var latch: CountDownLatch = CountDownLatch(1)
@Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
@Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+ @Captor
+ private lateinit var listingCallback:
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+ @Captor
+ private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>>
@Rule
@JvmField
@@ -58,11 +91,11 @@
) {
override fun create(intent: Intent?): TestableControlsFavoritingActivity {
return TestableControlsFavoritingActivity(
+ featureFlags,
executor,
controller,
listingController,
userTracker,
- uiController,
mockDispatcher,
latch
)
@@ -75,19 +108,18 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- val intent = Intent()
- intent.putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
- activityRule.launchActivity(intent)
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
}
// b/259549854 to root-cause and fix
@FlakyTest
@Test
fun testBackCallbackRegistrationAndUnregistration() {
+ launchActivity()
// 1. ensure that launching the activity results in it registering a callback
verify(mockDispatcher)
.registerOnBackInvokedCallback(
- ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
captureCallback.capture()
)
activityRule.finishActivity()
@@ -96,22 +128,116 @@
verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
}
- public class TestableControlsFavoritingActivity(
+ @Test
+ fun testNewFlowEnabled_buttons() {
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ with(launchActivity()) {
+ verify(listingController).addCallback(listingCallback.capture())
+ listingCallback.value.onServicesUpdated(
+ listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
+ )
+
+ val rearrangeButton = requireViewById<Button>(R.id.rearrange)
+ assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE)
+ assertThat(rearrangeButton.isEnabled).isFalse()
+ assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun testNewFlowDisabled_buttons() {
+ with(launchActivity()) {
+ verify(listingController).addCallback(listingCallback.capture())
+ activityRule.runOnUiThread {
+ listingCallback.value.onServicesUpdated(
+ listOf(
+ mock(ControlsServiceInfo::class.java),
+ mock(ControlsServiceInfo::class.java)
+ )
+ )
+ }
+
+ val rearrangeButton = requireViewById<Button>(R.id.rearrange)
+ assertThat(rearrangeButton.visibility).isEqualTo(View.GONE)
+ assertThat(rearrangeButton.isEnabled).isFalse()
+ assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() {
+ featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ with(launchActivity()) {
+ verify(listingController).addCallback(capture(listingCallback))
+ listingCallback.value.onServicesUpdated(
+ listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
+ )
+ verify(controller).loadForComponent(any(), capture(controlsCallback), any())
+ activityRule.runOnUiThread {
+ controlsCallback.value.accept(
+ createLoadDataObject(
+ listOf(ControlStatus(TEST_CONTROL, TEST_COMPONENT, true)),
+ emptyList(),
+ )
+ )
+ requireViewById<Button>(R.id.rearrange).performClick()
+ }
+
+ verify(controller).replaceFavoritesForStructure(any())
+ with(startActivityData!!.intent) {
+ assertThat(component)
+ .isEqualTo(ComponentName(context, ControlsEditingActivity::class.java))
+ assertThat(
+ getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+ )
+ .isEqualTo(TEST_COMPONENT)
+ assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_APP))
+ .isEqualTo(TEST_APP)
+ assertThat(getBooleanExtra(ControlsEditingActivity.EXTRA_FROM_FAVORITING, false))
+ .isTrue()
+ assertThat(getCharSequenceExtra(ControlsEditingActivity.EXTRA_STRUCTURE))
+ .isEqualTo("")
+ }
+ }
+ }
+
+ private fun launchActivity(
+ componentName: ComponentName = TEST_COMPONENT,
+ structure: CharSequence = TEST_STRUCTURE,
+ app: CharSequence = TEST_APP,
+ source: Byte = ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR,
+ ): TestableControlsFavoritingActivity =
+ activityRule.launchActivity(
+ Intent().apply {
+ putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
+ putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
+ putExtra(ControlsFavoritingActivity.EXTRA_APP, app)
+ putExtra(ControlsFavoritingActivity.EXTRA_SOURCE, source)
+ }
+ )
+
+ class TestableControlsFavoritingActivity(
+ featureFlags: FeatureFlags,
executor: Executor,
controller: ControlsControllerImpl,
listingController: ControlsListingController,
userTracker: UserTracker,
- uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
) :
ControlsFavoritingActivity(
+ featureFlags,
executor,
controller,
listingController,
userTracker,
- uiController
) {
+
+ var triedToFinish = false
+
+ var startActivityData: StartActivityData? = null
+ private set
+
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
return mockDispatcher
}
@@ -121,5 +247,17 @@
// ensures that test runner thread does not proceed until ui thread is done
latch.countDown()
}
+
+ override fun startActivity(intent: Intent) {
+ startActivityData = StartActivityData(intent, null)
+ }
+
+ override fun startActivity(intent: Intent, options: Bundle?) {
+ startActivityData = StartActivityData(intent, options)
+ }
+
+ override fun animateExitAndFinish() {
+ triedToFinish = true
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
new file mode 100644
index 0000000..977e3ba
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/StartActivityData.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.controls.management
+
+import android.content.Intent
+import android.os.Bundle
+
+data class StartActivityData(val intent: Intent, val options: Bundle?)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
index 6b3ec68..a7d7b93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -28,20 +28,29 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.Executor;
+
@SmallTest
public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase {
+ private Executor mExecutor;
private DozeMachine.Service mInner;
private DozeScreenStatePreventingAdapter mWrapper;
@Before
public void setup() throws Exception {
+ mExecutor = new FakeExecutor(new FakeSystemClock());
mInner = mock(DozeMachine.Service.class);
- mWrapper = new DozeScreenStatePreventingAdapter(mInner);
+ mWrapper = new DozeScreenStatePreventingAdapter(
+ mInner,
+ mExecutor
+ );
}
@Test
@@ -86,7 +95,8 @@
when(params.getDisplayStateSupported()).thenReturn(false);
assertEquals(DozeScreenStatePreventingAdapter.class,
- DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+ DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+ .getClass());
}
@Test
@@ -94,6 +104,7 @@
DozeParameters params = mock(DozeParameters.class);
when(params.getDisplayStateSupported()).thenReturn(true);
- assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+ assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
+ mExecutor));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
index 9ae7217..240d2d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -28,20 +28,26 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.Executor;
+
@SmallTest
public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase {
+ private Executor mExecutor;
private DozeMachine.Service mInner;
private DozeSuspendScreenStatePreventingAdapter mWrapper;
@Before
public void setup() throws Exception {
+ mExecutor = new FakeExecutor(new FakeSystemClock());
mInner = mock(DozeMachine.Service.class);
- mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner);
+ mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner, mExecutor);
}
@Test
@@ -92,7 +98,8 @@
when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false);
assertEquals(DozeSuspendScreenStatePreventingAdapter.class,
- DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+ DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+ .getClass());
}
@Test
@@ -100,6 +107,7 @@
DozeParameters params = mock(DozeParameters.class);
when(params.getDozeSuspendDisplayStateSupported()).thenReturn(true);
- assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+ assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
+ mExecutor));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 7f6e2ba..08427da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -399,7 +399,21 @@
}
@Test
- public void testPause() {
+ public void testPauseWithNoActiveSessions() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ environment.updateLifecycle(observerOwnerPair -> {
+ observerOwnerPair.first.onPause(observerOwnerPair.second);
+ });
+
+ environment.verifyInputSessionDispose();
+ }
+
+ @Test
+ public void testDeferredPauseWithActiveSessions() {
final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
final Environment environment = new Environment(Stream.of(touchHandler)
@@ -417,14 +431,59 @@
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
+ final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+
+ verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
+
environment.updateLifecycle(observerOwnerPair -> {
observerOwnerPair.first.onPause(observerOwnerPair.second);
});
+ verify(environment.mInputSession, never()).dispose();
+
+ // End session
+ touchSessionArgumentCaptor.getValue().pop();
+ environment.executeAll();
+
+ // Check to make sure the input session is now disposed.
environment.verifyInputSessionDispose();
}
@Test
+ public void testDestroyWithActiveSessions() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(touchHandler);
+
+ // First event will be missed since we register after the execution loop,
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(event);
+ verify(eventListener).onInputEvent(eq(event));
+
+ final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+
+ verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
+
+ environment.updateLifecycle(observerOwnerPair -> {
+ observerOwnerPair.first.onDestroy(observerOwnerPair.second);
+ });
+
+ // Check to make sure the input session is now disposed.
+ environment.verifyInputSessionDispose();
+ }
+
+
+ @Test
public void testPilfering() {
final DreamTouchHandler touchHandler1 = Mockito.mock(DreamTouchHandler.class);
final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
@@ -476,7 +535,7 @@
environment.executeAll();
environment.updateLifecycle(observerOwnerPair -> {
- observerOwnerPair.first.onPause(observerOwnerPair.second);
+ observerOwnerPair.first.onDestroy(observerOwnerPair.second);
});
environment.executeAll();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
new file mode 100644
index 0000000..5704ef3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.dreams.touch;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ShadeTouchHandlerTest extends SysuiTestCase {
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+
+ @Mock
+ NotificationPanelViewController mNotificationPanelViewController;
+
+ @Mock
+ DreamTouchHandler.TouchSession mTouchSession;
+
+ ShadeTouchHandler mTouchHandler;
+
+ private static final int TOUCH_HEIGHT = 20;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces),
+ TOUCH_HEIGHT);
+ when(mCentralSurfaces.getNotificationPanelViewController())
+ .thenReturn(mNotificationPanelViewController);
+ }
+
+ /**
+ * Verify that touches aren't handled when the bouncer is showing.
+ */
+ @Test
+ public void testInactiveOnBouncer() {
+ when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).pop();
+ }
+
+ /**
+ * Make sure {@link ShadeTouchHandler}
+ */
+ @Test
+ public void testTouchPilferingOnScroll() {
+ final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
+ final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture());
+
+ assertThat(gestureListenerArgumentCaptor.getValue()
+ .onScroll(motionEvent1, motionEvent2, 1, 1))
+ .isTrue();
+ }
+
+ /**
+ * Ensure touches are propagated to the {@link NotificationPanelViewController}.
+ */
+ @Test
+ public void testEventPropagation() {
+ final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
+
+ final ArgumentCaptor<InputChannelCompat.InputEventListener>
+ inputEventListenerArgumentCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+
+ mTouchHandler.onSessionStart(mTouchSession);
+ verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture());
+ inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent);
+ verify(mNotificationPanelViewController).handleExternalTouch(motionEvent);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
new file mode 100644
index 0000000..ccd631e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -0,0 +1,346 @@
+package com.android.systemui.graphics
+
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.ImageDecoder
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.VectorDrawable
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+class ImageLoaderTest : SysuiTestCase() {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val imageLoader = ImageLoader(context, testDispatcher)
+
+ private lateinit var imgFile: File
+
+ @Before
+ fun setUp() {
+ val context = context.createPackageContext("com.android.systemui.tests", 0)
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ com.android.systemui.tests.R.drawable.romainguy_rockaway
+ )
+
+ imgFile = File.createTempFile("image", ".png", context.cacheDir)
+ imgFile.deleteOnExit()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, FileOutputStream(imgFile))
+ }
+
+ @After
+ fun tearDown() {
+ imgFile.delete()
+ }
+
+ @Test
+ fun invalidResource_drawable_returnsNull() =
+ testScope.runTest { assertThat(imageLoader.loadDrawable(ImageLoader.Res(-1))).isNull() }
+
+ @Test
+ fun invalidResource_bitmap_returnsNull() =
+ testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Res(-1))).isNull() }
+
+ @Test
+ fun invalidUri_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadBitmap(ImageLoader.Uri("this.is/bogus"))).isNull()
+ }
+
+ @Test
+ fun invalidFile_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadBitmap(ImageLoader.File("this is broken!"))).isNull()
+ }
+
+ @Test
+ fun invalidIcon_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
+ }
+
+ @Test
+ fun invalidIS_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadDrawable(
+ ImageLoader.InputStream(ByteArrayInputStream(ByteArray(0)))
+ )
+ )
+ .isNull()
+ }
+
+ @Test
+ fun validBitmapResource_loadDrawable_returnsBitmapDrawable() =
+ testScope.runTest {
+ val context = context.createPackageContext("com.android.systemui.tests", 0)
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ com.android.systemui.tests.R.drawable.romainguy_rockaway
+ )
+ assertThat(bitmap).isNotNull()
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.Res(
+ com.android.systemui.tests.R.drawable.romainguy_rockaway,
+ context
+ )
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validBitmapResource_loadBitmap_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val loadedBitmap =
+ imageLoader.loadBitmap(ImageLoader.Res(R.drawable.dessert_zombiegingerbread))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapUri_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Uri(uri))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapFile_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.File(imgFile))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validInputStream_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
+ val loadedBitmap =
+ imageLoader.loadBitmap(ImageLoader.InputStream(FileInputStream(imgFile)))
+ assertBitmapEqualToBitmap(loadedBitmap, bitmap)
+ }
+
+ @Test
+ fun validBitmapIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithBitmap(bitmap))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validUriIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithContentUri(Uri.parse(uri)))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validDataIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val bos =
+ ByteArrayOutputStream(
+ bitmap.byteCount * 2
+ ) // Compressed bitmap should be smaller than its source.
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+ val array = bos.toByteArray()
+ val loadedDrawable = imageLoader.loadDrawable(Icon.createWithData(array, 0, array.size))
+ assertBitmapEqualToDrawable(loadedDrawable, bitmap)
+ }
+
+ @Test
+ fun validSystemResourceIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap =
+ Resources.getSystem().getDrawable(android.R.drawable.ic_dialog_alert, context.theme)
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource("android", android.R.drawable.ic_dialog_alert)
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+ }
+
+ @Test
+ fun invalidDifferentPackageResourceIcon_returnsNull() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "noooope.wrong.package",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ assertThat(loadedDrawable).isNull()
+ }
+
+ @Test
+ fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 160)
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(160)
+ assertThat(loadedBitmap.height).isEqualTo(106)
+ }
+
+ @Test
+ fun validBitmapResource_heightMoreRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(ImageLoader.File(imgFile), maxWidth = 160, maxHeight = 50)
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(74)
+ assertThat(loadedBitmap.height).isEqualTo(50)
+ }
+
+ @Test
+ fun validBitmapResource_onlyWidthRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ maxWidth = 160,
+ maxHeight = ImageLoader.DO_NOT_RESIZE
+ )
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(160)
+ assertThat(loadedBitmap.height).isEqualTo(106)
+ }
+
+ @Test
+ fun validBitmapResource_onlyHeightRestricted_downsizesKeepingAspectRatio() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.Res(R.drawable.bubble_thumbnail),
+ maxWidth = ImageLoader.DO_NOT_RESIZE,
+ maxHeight = 120
+ )
+ val loadedBitmap = assertBitmapInDrawable(loadedDrawable)
+ assertThat(loadedBitmap.width).isEqualTo(123)
+ assertThat(loadedBitmap.height).isEqualTo(120)
+ }
+
+ @Test
+ fun validVectorDrawable_loadDrawable_successfullyLoaded() =
+ testScope.runTest {
+ val loadedDrawable = imageLoader.loadDrawable(ImageLoader.Res(R.drawable.ic_settings))
+ assertThat(loadedDrawable).isNotNull()
+ assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+ }
+
+ @Test
+ fun validVectorDrawable_loadBitmap_returnsNull() =
+ testScope.runTest {
+ val loadedBitmap = imageLoader.loadBitmap(ImageLoader.Res(R.drawable.ic_settings))
+ assertThat(loadedBitmap).isNull()
+ }
+
+ @Test
+ fun validVectorDrawableIcon_loadDrawable_successfullyLoaded() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(Icon.createWithResource(context, R.drawable.ic_settings))
+ assertThat(loadedDrawable).isNotNull()
+ assertThat(loadedDrawable).isInstanceOf(VectorDrawable::class.java)
+ }
+
+ @Test
+ fun hardwareAllocator_returnsHardwareBitmap() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ allocator = ImageDecoder.ALLOCATOR_HARDWARE
+ )
+ assertThat(loadedDrawable).isNotNull()
+ assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+ .isEqualTo(Bitmap.Config.HARDWARE)
+ }
+
+ @Test
+ fun softwareAllocator_returnsSoftwareBitmap() =
+ testScope.runTest {
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ ImageLoader.File(imgFile),
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ )
+ assertThat(loadedDrawable).isNotNull()
+ assertThat((loadedDrawable as BitmapDrawable).bitmap.config)
+ .isNotEqualTo(Bitmap.Config.HARDWARE)
+ }
+
+ private fun assertBitmapInDrawable(drawable: Drawable?): Bitmap {
+ assertThat(drawable).isNotNull()
+ assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
+ return (drawable as BitmapDrawable).bitmap
+ }
+
+ private fun assertBitmapEqualToDrawable(actual: Drawable?, expected: Bitmap) {
+ val actualBitmap = assertBitmapInDrawable(actual)
+ assertBitmapEqualToBitmap(actualBitmap, expected)
+ }
+
+ private fun assertBitmapEqualToBitmap(actual: Bitmap?, expected: Bitmap) {
+ assertThat(actual).isNotNull()
+ assertThat(actual?.width).isEqualTo(expected.width)
+ assertThat(actual?.height).isEqualTo(expected.height)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index fb7d379..5d83f56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -157,11 +157,34 @@
assertThat(strongBiometricAllowed()).isFalse()
}
+ @Test
+ fun convenienceBiometricAllowedChange() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ val convenienceBiometricAllowed =
+ collectLastValue(underTest.isNonStrongBiometricAllowed)
+ runCurrent()
+
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+ assertThat(convenienceBiometricAllowed()).isTrue()
+
+ onNonStrongAuthChanged(false, ANOTHER_USER_ID)
+ assertThat(convenienceBiometricAllowed()).isTrue()
+
+ onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+ assertThat(convenienceBiometricAllowed()).isFalse()
+ }
+
private fun onStrongAuthChanged(flags: Int, userId: Int) {
strongAuthTracker.value.stub.onStrongAuthRequiredChanged(flags, userId)
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
}
+ private fun onNonStrongAuthChanged(allowed: Boolean, userId: Int) {
+ strongAuthTracker.value.stub.onIsNonStrongBiometricAllowedChanged(allowed, userId)
+ testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+ }
+
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
new file mode 100644
index 0000000..6e002f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -0,0 +1,920 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+import android.app.StatusBarManager.SESSION_KEYGUARD
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_PRIMARY
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.ComponentInfoInternal
+import android.hardware.face.FaceAuthenticateOptions
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.os.CancellationSignal
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId.fakeInstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.FakeKeyguardStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
+
+ @Mock private lateinit var faceManager: FaceManager
+ @Mock private lateinit var bypassController: KeyguardBypassController
+ @Mock private lateinit var sessionTracker: SessionTracker
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var dumpManager: DumpManager
+
+ @Captor
+ private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
+
+ @Captor
+ private lateinit var detectionCallback: ArgumentCaptor<FaceManager.FaceDetectionCallback>
+ @Captor private lateinit var cancellationSignal: ArgumentCaptor<CancellationSignal>
+
+ private lateinit var bypassStateChangedListener:
+ KotlinArgumentCaptor<KeyguardBypassController.OnBypassStateChangedListener>
+
+ @Captor
+ private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
+ private lateinit var testDispatcher: TestDispatcher
+
+ private lateinit var testScope: TestScope
+ private lateinit var fakeUserRepository: FakeUserRepository
+ private lateinit var authStatus: FlowValue<AuthenticationStatus?>
+ private lateinit var detectStatus: FlowValue<DetectionStatus?>
+ private lateinit var authRunning: FlowValue<Boolean?>
+ private lateinit var lockedOut: FlowValue<Boolean?>
+ private lateinit var canFaceAuthRun: FlowValue<Boolean?>
+ private lateinit var authenticated: FlowValue<Boolean?>
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var deviceEntryFingerprintAuthRepository:
+ FakeDeviceEntryFingerprintAuthRepository
+ private lateinit var trustRepository: FakeTrustRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+ private lateinit var fakeCommandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+
+ private var wasAuthCancelled = false
+ private var wasDetectCancelled = false
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ fakeUserRepository = FakeUserRepository()
+ fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
+ testDispatcher = StandardTestDispatcher()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+ trustRepository = FakeTrustRepository()
+ keyguardRepository = FakeKeyguardRepository()
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+ fakeCommandQueue = FakeCommandQueue()
+ keyguardInteractor =
+ KeyguardInteractor(
+ keyguardRepository,
+ fakeCommandQueue,
+ featureFlags,
+ bouncerRepository
+ )
+ alternateBouncerInteractor =
+ AlternateBouncerInteractor(
+ bouncerRepository = bouncerRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+ systemClock = mock(SystemClock::class.java),
+ keyguardStateController = FakeKeyguardStateController(),
+ statusBarStateController = mock(StatusBarStateController::class.java),
+ )
+
+ bypassStateChangedListener =
+ KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
+ testScope = TestScope(testDispatcher)
+ whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+ whenever(bypassController.bypassEnabled).thenReturn(true)
+ underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
+ }
+
+ private fun createDeviceEntryFaceAuthRepositoryImpl(
+ fmOverride: FaceManager? = faceManager,
+ bypassControllerOverride: KeyguardBypassController? = bypassController
+ ) =
+ DeviceEntryFaceAuthRepositoryImpl(
+ mContext,
+ fmOverride,
+ fakeUserRepository,
+ bypassControllerOverride,
+ testScope.backgroundScope,
+ testDispatcher,
+ sessionTracker,
+ uiEventLogger,
+ FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")),
+ biometricSettingsRepository,
+ deviceEntryFingerprintAuthRepository,
+ trustRepository,
+ keyguardRepository,
+ keyguardInteractor,
+ alternateBouncerInteractor,
+ dumpManager,
+ )
+
+ @Test
+ fun faceAuthRunsAndProvidesAuthStatusUpdates() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+ uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+ assertThat(authRunning()).isTrue()
+
+ val successResult = successResult()
+ authenticationCallback.value.onAuthenticationSucceeded(successResult)
+
+ assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+ assertThat(authenticated()).isTrue()
+ assertThat(authRunning()).isFalse()
+ }
+
+ private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
+ verify(uiEventLogger)
+ .logWithInstanceIdAndPosition(
+ faceAuthUiEvent,
+ 0,
+ null,
+ keyguardSessionId,
+ faceAuthUiEvent.extraInfo
+ )
+ }
+
+ @Test
+ fun faceAuthDoesNotRunWhileItIsAlreadyRunning() =
+ testScope.runTest {
+ initCollectors()
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+ clearInvocations(faceManager)
+ clearInvocations(uiEventLogger)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ verifyNoMoreInteractions(faceManager)
+ verifyNoMoreInteractions(uiEventLogger)
+ }
+
+ @Test
+ fun faceLockoutStatusIsPropagated() =
+ testScope.runTest {
+ initCollectors()
+ verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_LOCKOUT_PERMANENT,
+ "face locked out"
+ )
+
+ assertThat(lockedOut()).isTrue()
+
+ faceLockoutResetCallback.value.onLockoutReset(0)
+ assertThat(lockedOut()).isFalse()
+ }
+
+ @Test
+ fun faceDetectionSupportIsTheCorrectValue() =
+ testScope.runTest {
+ assertThat(
+ createDeviceEntryFaceAuthRepositoryImpl(fmOverride = null).isDetectionSupported
+ )
+ .isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
+ assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
+ assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
+ assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(
+ listOf(
+ createFaceSensorProperties(supportsFaceDetection = false),
+ createFaceSensorProperties(supportsFaceDetection = true)
+ )
+ )
+ assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
+
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(
+ listOf(
+ createFaceSensorProperties(supportsFaceDetection = true),
+ createFaceSensorProperties(supportsFaceDetection = false)
+ )
+ )
+ assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isTrue()
+ }
+
+ @Test
+ fun cancelStopsFaceAuthentication() =
+ testScope.runTest {
+ initCollectors()
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ var wasAuthCancelled = false
+ cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
+
+ underTest.cancel()
+ assertThat(wasAuthCancelled).isTrue()
+ assertThat(authRunning()).isFalse()
+ }
+
+ @Test
+ fun cancelInvokedWithoutFaceAuthRunningIsANoop() = testScope.runTest { underTest.cancel() }
+
+ @Test
+ fun faceDetectionRunsAndPropagatesDetectionStatus() =
+ testScope.runTest {
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+ underTest = createDeviceEntryFaceAuthRepositoryImpl()
+ initCollectors()
+
+ underTest.detect()
+ faceDetectIsCalled()
+
+ detectionCallback.value.onFaceDetected(1, 1, true)
+
+ assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+ }
+
+ @Test
+ fun faceDetectDoesNotRunIfDetectionIsNotSupported() =
+ testScope.runTest {
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
+ underTest = createDeviceEntryFaceAuthRepositoryImpl()
+ initCollectors()
+ clearInvocations(faceManager)
+
+ underTest.detect()
+
+ verify(faceManager, never())
+ .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java))
+ }
+
+ @Test
+ fun faceAuthShouldWaitAndRunIfTriggeredWhileCancelling() =
+ testScope.runTest {
+ initCollectors()
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ // Enter cancelling state
+ underTest.cancel()
+ clearInvocations(faceManager)
+
+ // Auth is while cancelling.
+ underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+ // Auth is not started
+ verifyNoMoreInteractions(faceManager)
+
+ // Auth is done cancelling.
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_CANCELED,
+ "First auth attempt cancellation completed"
+ )
+ assertThat(authStatus())
+ .isEqualTo(
+ ErrorAuthenticationStatus(
+ FACE_ERROR_CANCELED,
+ "First auth attempt cancellation completed"
+ )
+ )
+
+ faceAuthenticateIsCalled()
+ uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+ }
+
+ @Test
+ fun faceAuthAutoCancelsAfterDefaultCancellationTimeout() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ clearInvocations(faceManager)
+ underTest.cancel()
+ advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+ }
+
+ @Test
+ fun faceHelpMessagesAreIgnoredBasedOnConfig() =
+ testScope.runTest {
+ overrideResource(
+ R.array.config_face_acquire_device_entry_ignorelist,
+ intArrayOf(10, 11)
+ )
+ underTest = createDeviceEntryFaceAuthRepositoryImpl()
+ initCollectors()
+
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ faceAuthenticateIsCalled()
+
+ authenticationCallback.value.onAuthenticationHelp(9, "help msg")
+ authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
+ authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
+
+ assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+ }
+
+ @Test
+ fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
+ testScope.runTest {
+ fakeUserRepository.setSelectedUserInfo(primaryUser)
+ underTest.dump(PrintWriter(StringWriter()), emptyArray())
+
+ underTest =
+ createDeviceEntryFaceAuthRepositoryImpl(
+ fmOverride = null,
+ bypassControllerOverride = null
+ )
+ fakeUserRepository.setSelectedUserInfo(primaryUser)
+
+ underTest.dump(PrintWriter(StringWriter()), emptyArray())
+ }
+
+ @Test
+ fun authenticateDoesNotRunIfFaceIsNotEnrolled() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { biometricSettingsRepository.setFaceEnrolled(false) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunIfFaceIsNotEnabled() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunIfUserIsInLockdown() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { biometricSettingsRepository.setIsUserInLockdown(true) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunIfUserIsCurrentlySwitching() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenFpIsLockedOut() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { deviceEntryFingerprintAuthRepository.setLockedOut(true) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ state = WakefulnessState.STARTING_TO_SLEEP,
+ isWakingUpOrAwake = false,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenDeviceIsSleeping() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ state = WakefulnessState.ASLEEP,
+ isWakingUpOrAwake = false,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+ }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenCurrentUserIsNotPrimary() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ launch { fakeUserRepository.setSelectedUserInfo(secondaryUser) }
+ }
+ }
+
+ @Test
+ fun authenticateDoesNotRunWhenSecureCameraIsActive() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ bouncerRepository.setAlternateVisible(false)
+ fakeCommandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ }
+ }
+ }
+
+ @Test
+ fun authenticateDoesNotRunOnUnsupportedPosture() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(false)
+ }
+ }
+
+ @Test
+ fun authenticateFallbacksToDetectionWhenItCannotRun() =
+ testScope.runTest {
+ whenever(faceManager.sensorPropertiesInternal)
+ .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+ whenever(bypassController.bypassEnabled).thenReturn(true)
+ underTest = createDeviceEntryFaceAuthRepositoryImpl()
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ // Flip one precondition to false.
+ biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+ assertThat(canFaceAuthRun()).isFalse()
+ underTest.authenticate(
+ FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+ fallbackToDetection = true
+ )
+ faceAuthenticateIsNotCalled()
+
+ faceDetectIsCalled()
+ }
+
+ @Test
+ fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
+ testScope.runTest {
+ featureFlags.set(FACE_AUTH_REFACTOR, false)
+
+ underTest = createDeviceEntryFaceAuthRepositoryImpl()
+ initCollectors()
+
+ // Collecting any flows exposed in the public API doesn't throw any error
+ authStatus()
+ detectStatus()
+ authRunning()
+ lockedOut()
+ canFaceAuthRun()
+ authenticated()
+ }
+
+ @Test
+ fun isAuthenticatedIsFalseWhenFaceAuthFails() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ triggerFaceAuth(false)
+
+ authenticationCallback.value.onAuthenticationFailed()
+
+ assertThat(authenticated()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticatedIsFalseWhenFaceAuthErrorsOut() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ triggerFaceAuth(false)
+
+ authenticationCallback.value.onAuthenticationError(-1, "some error")
+
+ assertThat(authenticated()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticatedIsResetToFalseWhenKeyguardIsGoingAway() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ triggerFaceAuth(false)
+
+ authenticationCallback.value.onAuthenticationSucceeded(
+ mock(FaceManager.AuthenticationResult::class.java)
+ )
+
+ assertThat(authenticated()).isTrue()
+
+ keyguardRepository.setKeyguardGoingAway(true)
+
+ assertThat(authenticated()).isFalse()
+ }
+
+ @Test
+ fun isAuthenticatedIsResetToFalseWhenUserIsSwitching() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ triggerFaceAuth(false)
+
+ authenticationCallback.value.onAuthenticationSucceeded(
+ mock(FaceManager.AuthenticationResult::class.java)
+ )
+
+ assertThat(authenticated()).isTrue()
+
+ fakeUserRepository.setUserSwitching(true)
+
+ assertThat(authenticated()).isFalse()
+ }
+
+ @Test
+ fun detectDoesNotRunWhenFaceIsNotEnrolled() =
+ testScope.runTest {
+ testGatingCheckForDetect { biometricSettingsRepository.setFaceEnrolled(false) }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenFaceIsNotEnabled() =
+ testScope.runTest {
+ testGatingCheckForDetect { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenUserSwitchingInProgress() =
+ testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } }
+
+ @Test
+ fun detectDoesNotRunWhenKeyguardGoingAway() =
+ testScope.runTest {
+ testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
+ testScope.runTest {
+ testGatingCheckForDetect {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ state = WakefulnessState.STARTING_TO_SLEEP,
+ isWakingUpOrAwake = false,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenSecureCameraIsActive() =
+ testScope.runTest {
+ testGatingCheckForDetect {
+ bouncerRepository.setAlternateVisible(false)
+ fakeCommandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ }
+ }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenFaceAuthNotSupportedInCurrentPosture() =
+ testScope.runTest {
+ testGatingCheckForDetect {
+ biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(false)
+ }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenCurrentUserInLockdown() =
+ testScope.runTest {
+ testGatingCheckForDetect { biometricSettingsRepository.setIsUserInLockdown(true) }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenBypassIsNotEnabled() =
+ testScope.runTest {
+ runCurrent()
+ verify(bypassController)
+ .registerOnBypassStateChangedListener(bypassStateChangedListener.capture())
+
+ testGatingCheckForDetect {
+ bypassStateChangedListener.value.onBypassStateChanged(false)
+ }
+ }
+
+ @Test
+ fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
+ testScope.runTest {
+ testGatingCheckForDetect {
+ biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+ }
+ }
+
+ @Test
+ fun detectDoesNotRunIfUdfpsIsRunning() =
+ testScope.runTest {
+ testGatingCheckForDetect {
+ deviceEntryFingerprintAuthRepository.setAvailableFpSensorType(
+ BiometricType.UNDER_DISPLAY_FINGERPRINT
+ )
+ deviceEntryFingerprintAuthRepository.setIsRunning(true)
+ }
+ }
+
+ private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ gatingCheckModifier()
+ runCurrent()
+
+ // gating check doesn't allow face auth to run.
+ assertThat(underTest.canRunFaceAuth.value).isFalse()
+
+ // flip the gating check back on.
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ triggerFaceAuth(false)
+
+ // Flip gating check off
+ gatingCheckModifier()
+ runCurrent()
+
+ // Stops currently running auth
+ assertThat(wasAuthCancelled).isTrue()
+ clearInvocations(faceManager)
+
+ // Try auth again
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+ // Auth can't run again
+ faceAuthenticateIsNotCalled()
+ }
+
+ private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ // This will stop face auth from running but is required to be false for detect.
+ biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+ runCurrent()
+
+ assertThat(canFaceAuthRun()).isFalse()
+
+ // Trigger authenticate with detection fallback
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+
+ faceAuthenticateIsNotCalled()
+ faceDetectIsCalled()
+ cancellationSignal.value.setOnCancelListener { wasDetectCancelled = true }
+
+ // Flip gating check
+ gatingCheckModifier()
+ runCurrent()
+
+ // Stops currently running detect
+ assertThat(wasDetectCancelled).isTrue()
+ clearInvocations(faceManager)
+
+ // Try to run detect again
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+
+ // Detect won't run because preconditions are not true anymore.
+ faceDetectIsNotCalled()
+ }
+
+ private suspend fun triggerFaceAuth(fallbackToDetect: Boolean) {
+ assertThat(canFaceAuthRun()).isTrue()
+ underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect)
+ faceAuthenticateIsCalled()
+ assertThat(authRunning()).isTrue()
+ cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
+ }
+
+ private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+ biometricSettingsRepository.setFaceEnrolled(true)
+ biometricSettingsRepository.setIsFaceAuthEnabled(true)
+ fakeUserRepository.setUserSwitching(false)
+ deviceEntryFingerprintAuthRepository.setLockedOut(false)
+ trustRepository.setCurrentUserTrusted(false)
+ keyguardRepository.setKeyguardGoingAway(false)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.STARTING_TO_WAKE,
+ true,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+ biometricSettingsRepository.setIsUserInLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(primaryUser)
+ biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ }
+
+ private suspend fun TestScope.initCollectors() {
+ authStatus = collectLastValue(underTest.authenticationStatus)
+ detectStatus = collectLastValue(underTest.detectionStatus)
+ authRunning = collectLastValue(underTest.isAuthRunning)
+ lockedOut = collectLastValue(underTest.isLockedOut)
+ canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
+ authenticated = collectLastValue(underTest.isAuthenticated)
+ fakeUserRepository.setSelectedUserInfo(primaryUser)
+ }
+
+ private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false)
+
+ private fun faceDetectIsCalled() {
+ verify(faceManager)
+ .detectFace(
+ cancellationSignal.capture(),
+ detectionCallback.capture(),
+ eq(FaceAuthenticateOptions.Builder().setUserId(primaryUserId).build())
+ )
+ }
+
+ private fun faceAuthenticateIsCalled() {
+ verify(faceManager)
+ .authenticate(
+ isNull(),
+ cancellationSignal.capture(),
+ authenticationCallback.capture(),
+ isNull(),
+ eq(FaceAuthenticateOptions.Builder().setUserId(primaryUserId).build())
+ )
+ }
+
+ private fun faceAuthenticateIsNotCalled() {
+ verify(faceManager, never())
+ .authenticate(
+ isNull(),
+ any(),
+ any(),
+ isNull(),
+ any(FaceAuthenticateOptions::class.java)
+ )
+ }
+
+ private fun faceDetectIsNotCalled() {
+ verify(faceManager, never())
+ .detectFace(any(), any(), any(FaceAuthenticateOptions::class.java))
+ }
+
+ private fun createFaceSensorProperties(
+ supportsFaceDetection: Boolean
+ ): FaceSensorPropertiesInternal {
+ val componentInfo =
+ listOf(
+ ComponentInfoInternal(
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
+ )
+ )
+ return FaceSensorPropertiesInternal(
+ 0 /* id */,
+ FaceSensorProperties.STRENGTH_STRONG,
+ 1 /* maxTemplatesAllowed */,
+ componentInfo,
+ FaceSensorProperties.TYPE_UNKNOWN,
+ supportsFaceDetection /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */
+ )
+ }
+
+ companion object {
+ const val primaryUserId = 1
+ val keyguardSessionId = fakeInstanceId(10)!!
+ val primaryUser = UserInfo(primaryUserId, "test user", FLAG_PRIMARY)
+
+ val secondaryUser = UserInfo(2, "secondary user", 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 70f766f..e57b044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data.repository
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
import android.hardware.biometrics.BiometricSourceType
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
@@ -30,7 +31,6 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,11 +70,6 @@
)
}
- @After
- fun tearDown() {
- // verify(keyguardUpdateMonitor).removeCallback(updateMonitorCallback.value)
- }
-
@Test
fun isLockedOut_whenFingerprintLockoutStateChanges_emitsNewValue() =
testScope.runTest {
@@ -129,29 +124,55 @@
}
@Test
- fun enabledFingerprintTypeProvidesTheCorrectOutput() =
+ fun enabledFingerprintTypeProvidesTheCorrectOutputForSpfs() =
testScope.runTest {
whenever(authController.isSfpsSupported).thenReturn(true)
whenever(authController.isUdfpsSupported).thenReturn(false)
whenever(authController.isRearFpsSupported).thenReturn(false)
- assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.SIDE_FINGERPRINT)
+ val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+ assertThat(availableFpSensorType()).isEqualTo(BiometricType.SIDE_FINGERPRINT)
+ }
+ @Test
+ fun enabledFingerprintTypeProvidesTheCorrectOutputForUdfps() =
+ testScope.runTest {
whenever(authController.isSfpsSupported).thenReturn(false)
whenever(authController.isUdfpsSupported).thenReturn(true)
whenever(authController.isRearFpsSupported).thenReturn(false)
+ val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+ assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
+ }
- assertThat(underTest.availableFpSensorType)
- .isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
-
+ @Test
+ fun enabledFingerprintTypeProvidesTheCorrectOutputForRearFps() =
+ testScope.runTest {
whenever(authController.isSfpsSupported).thenReturn(false)
whenever(authController.isUdfpsSupported).thenReturn(false)
whenever(authController.isRearFpsSupported).thenReturn(true)
- assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.REAR_FINGERPRINT)
+ val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+ assertThat(availableFpSensorType()).isEqualTo(BiometricType.REAR_FINGERPRINT)
+ }
+
+ @Test
+ fun enabledFingerprintTypeProvidesTheCorrectOutputAfterAllAuthenticatorsAreRegistered() =
+ testScope.runTest {
+ whenever(authController.isSfpsSupported).thenReturn(false)
+ whenever(authController.isUdfpsSupported).thenReturn(false)
whenever(authController.isRearFpsSupported).thenReturn(false)
+ whenever(authController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(false)
- assertThat(underTest.availableFpSensorType).isNull()
+ val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
+ runCurrent()
+
+ val callback = ArgumentCaptor.forClass(AuthController.Callback::class.java)
+ verify(authController).addCallback(callback.capture())
+ assertThat(availableFpSensorType()).isNull()
+
+ whenever(authController.isUdfpsSupported).thenReturn(true)
+ callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
+ assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt
deleted file mode 100644
index d55370b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManagerTest.kt
+++ /dev/null
@@ -1,427 +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.keyguard.data.repository
-
-import android.app.StatusBarManager.SESSION_KEYGUARD
-import android.content.pm.UserInfo
-import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
-import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
-import android.hardware.biometrics.ComponentInfoInternal
-import android.hardware.face.FaceAuthenticateOptions
-import android.hardware.face.FaceManager
-import android.hardware.face.FaceSensorProperties
-import android.hardware.face.FaceSensorPropertiesInternal
-import android.os.CancellationSignal
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId.fakeInstanceId
-import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.FlowValue
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
-import com.android.systemui.log.FaceAuthenticationLogger
-import com.android.systemui.log.SessionTracker
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import java.io.PrintWriter
-import java.io.StringWriter
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.isNull
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardFaceAuthManagerTest : SysuiTestCase() {
- private lateinit var underTest: KeyguardFaceAuthManagerImpl
-
- @Mock private lateinit var faceManager: FaceManager
- @Mock private lateinit var bypassController: KeyguardBypassController
- @Mock private lateinit var sessionTracker: SessionTracker
- @Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var dumpManager: DumpManager
-
- @Captor
- private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
- @Captor
- private lateinit var detectionCallback: ArgumentCaptor<FaceManager.FaceDetectionCallback>
- @Captor private lateinit var cancellationSignal: ArgumentCaptor<CancellationSignal>
- @Captor
- private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
- private lateinit var testDispatcher: TestDispatcher
-
- private lateinit var testScope: TestScope
- private lateinit var fakeUserRepository: FakeUserRepository
- private lateinit var authStatus: FlowValue<AuthenticationStatus?>
- private lateinit var detectStatus: FlowValue<DetectionStatus?>
- private lateinit var authRunning: FlowValue<Boolean?>
- private lateinit var lockedOut: FlowValue<Boolean?>
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- fakeUserRepository = FakeUserRepository()
- fakeUserRepository.setUserInfos(listOf(currentUser))
- testDispatcher = StandardTestDispatcher()
- testScope = TestScope(testDispatcher)
- whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
- whenever(bypassController.bypassEnabled).thenReturn(true)
- underTest = createFaceAuthManagerImpl(faceManager)
- }
-
- private fun createFaceAuthManagerImpl(
- fmOverride: FaceManager? = faceManager,
- bypassControllerOverride: KeyguardBypassController? = bypassController
- ) =
- KeyguardFaceAuthManagerImpl(
- mContext,
- fmOverride,
- fakeUserRepository,
- bypassControllerOverride,
- testScope.backgroundScope,
- testDispatcher,
- sessionTracker,
- uiEventLogger,
- FaceAuthenticationLogger(logcatLogBuffer("KeyguardFaceAuthManagerLog")),
- dumpManager,
- )
-
- @Test
- fun faceAuthRunsAndProvidesAuthStatusUpdates() =
- testScope.runTest {
- testSetup(this)
-
- FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
- uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
-
- assertThat(authRunning()).isTrue()
-
- val successResult = successResult()
- authenticationCallback.value.onAuthenticationSucceeded(successResult)
-
- assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
-
- assertThat(authRunning()).isFalse()
- }
-
- private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
- verify(uiEventLogger)
- .logWithInstanceIdAndPosition(
- faceAuthUiEvent,
- 0,
- null,
- keyguardSessionId,
- faceAuthUiEvent.extraInfo
- )
- }
-
- @Test
- fun faceAuthDoesNotRunWhileItIsAlreadyRunning() =
- testScope.runTest {
- testSetup(this)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
- clearInvocations(faceManager)
- clearInvocations(uiEventLogger)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- verifyNoMoreInteractions(faceManager)
- verifyNoMoreInteractions(uiEventLogger)
- }
-
- @Test
- fun faceLockoutStatusIsPropagated() =
- testScope.runTest {
- testSetup(this)
- verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- authenticationCallback.value.onAuthenticationError(
- FACE_ERROR_LOCKOUT_PERMANENT,
- "face locked out"
- )
-
- assertThat(lockedOut()).isTrue()
-
- faceLockoutResetCallback.value.onLockoutReset(0)
- assertThat(lockedOut()).isFalse()
- }
-
- @Test
- fun faceDetectionSupportIsTheCorrectValue() =
- testScope.runTest {
- assertThat(createFaceAuthManagerImpl(fmOverride = null).isDetectionSupported).isFalse()
-
- whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
- assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
- whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
- assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
- whenever(faceManager.sensorPropertiesInternal)
- .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
- assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
- whenever(faceManager.sensorPropertiesInternal)
- .thenReturn(
- listOf(
- createFaceSensorProperties(supportsFaceDetection = false),
- createFaceSensorProperties(supportsFaceDetection = true)
- )
- )
- assertThat(createFaceAuthManagerImpl().isDetectionSupported).isFalse()
-
- whenever(faceManager.sensorPropertiesInternal)
- .thenReturn(
- listOf(
- createFaceSensorProperties(supportsFaceDetection = true),
- createFaceSensorProperties(supportsFaceDetection = false)
- )
- )
- assertThat(createFaceAuthManagerImpl().isDetectionSupported).isTrue()
- }
-
- @Test
- fun cancelStopsFaceAuthentication() =
- testScope.runTest {
- testSetup(this)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- var wasAuthCancelled = false
- cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
-
- underTest.cancel()
- assertThat(wasAuthCancelled).isTrue()
- assertThat(authRunning()).isFalse()
- }
-
- @Test
- fun cancelInvokedWithoutFaceAuthRunningIsANoop() = testScope.runTest { underTest.cancel() }
-
- @Test
- fun faceDetectionRunsAndPropagatesDetectionStatus() =
- testScope.runTest {
- whenever(faceManager.sensorPropertiesInternal)
- .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
- underTest = createFaceAuthManagerImpl()
- testSetup(this)
-
- underTest.detect()
- faceDetectIsCalled()
-
- detectionCallback.value.onFaceDetected(1, 1, true)
-
- assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
- }
-
- @Test
- fun faceDetectDoesNotRunIfDetectionIsNotSupported() =
- testScope.runTest {
- whenever(faceManager.sensorPropertiesInternal)
- .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = false)))
- underTest = createFaceAuthManagerImpl()
- testSetup(this)
- clearInvocations(faceManager)
-
- underTest.detect()
-
- verify(faceManager, never()).detectFace(any(), any(), any())
- }
-
- @Test
- fun faceAuthShouldWaitAndRunIfTriggeredWhileCancelling() =
- testScope.runTest {
- testSetup(this)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- // Enter cancelling state
- underTest.cancel()
- clearInvocations(faceManager)
-
- // Auth is while cancelling.
- underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
- // Auth is not started
- verifyNoMoreInteractions(faceManager)
-
- // Auth is done cancelling.
- authenticationCallback.value.onAuthenticationError(
- FACE_ERROR_CANCELED,
- "First auth attempt cancellation completed"
- )
- assertThat(authStatus())
- .isEqualTo(
- ErrorAuthenticationStatus(
- FACE_ERROR_CANCELED,
- "First auth attempt cancellation completed"
- )
- )
-
- faceAuthenticateIsCalled()
- uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
- }
-
- @Test
- fun faceAuthAutoCancelsAfterDefaultCancellationTimeout() =
- testScope.runTest {
- testSetup(this)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- clearInvocations(faceManager)
- underTest.cancel()
- advanceTimeBy(KeyguardFaceAuthManagerImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
- }
-
- @Test
- fun faceHelpMessagesAreIgnoredBasedOnConfig() =
- testScope.runTest {
- overrideResource(
- R.array.config_face_acquire_device_entry_ignorelist,
- intArrayOf(10, 11)
- )
- underTest = createFaceAuthManagerImpl()
- testSetup(this)
-
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
- faceAuthenticateIsCalled()
-
- authenticationCallback.value.onAuthenticationHelp(9, "help msg")
- authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
- authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
-
- assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
- }
-
- @Test
- fun dumpDoesNotErrorOutWhenFaceManagerOrBypassControllerIsNull() =
- testScope.runTest {
- fakeUserRepository.setSelectedUserInfo(currentUser)
- underTest.dump(PrintWriter(StringWriter()), emptyArray())
-
- underTest =
- createFaceAuthManagerImpl(fmOverride = null, bypassControllerOverride = null)
- fakeUserRepository.setSelectedUserInfo(currentUser)
-
- underTest.dump(PrintWriter(StringWriter()), emptyArray())
- }
-
- private suspend fun testSetup(testScope: TestScope) {
- with(testScope) {
- authStatus = collectLastValue(underTest.authenticationStatus)
- detectStatus = collectLastValue(underTest.detectionStatus)
- authRunning = collectLastValue(underTest.isAuthRunning)
- lockedOut = collectLastValue(underTest.isLockedOut)
- fakeUserRepository.setSelectedUserInfo(currentUser)
- }
- }
-
- private fun successResult() = FaceManager.AuthenticationResult(null, null, currentUserId, false)
-
- private fun faceDetectIsCalled() {
- verify(faceManager)
- .detectFace(
- cancellationSignal.capture(),
- detectionCallback.capture(),
- eq(FaceAuthenticateOptions.Builder().setUserId(currentUserId).build())
- )
- }
-
- private fun faceAuthenticateIsCalled() {
- verify(faceManager)
- .authenticate(
- isNull(),
- cancellationSignal.capture(),
- authenticationCallback.capture(),
- isNull(),
- eq(FaceAuthenticateOptions.Builder().setUserId(currentUserId).build())
- )
- }
-
- private fun createFaceSensorProperties(
- supportsFaceDetection: Boolean
- ): FaceSensorPropertiesInternal {
- val componentInfo =
- listOf(
- ComponentInfoInternal(
- "faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */,
- "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */,
- "" /* softwareVersion */
- )
- )
- return FaceSensorPropertiesInternal(
- 0 /* id */,
- FaceSensorProperties.STRENGTH_STRONG,
- 1 /* maxTemplatesAllowed */,
- componentInfo,
- FaceSensorProperties.TYPE_UNKNOWN,
- supportsFaceDetection /* supportsFaceDetection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresChallenge */
- )
- }
-
- companion object {
- const val currentUserId = 1
- val keyguardSessionId = fakeInstanceId(10)!!
- val currentUser = UserInfo(currentUserId, "test user", 0)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 7f30162..68d694a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,18 +18,16 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
-import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.CommandQueue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.test.TestScope
@@ -38,7 +36,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@SmallTest
@@ -56,7 +53,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
- commandQueue = FakeCommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java))
+ commandQueue = FakeCommandQueue()
testScope = TestScope()
repository = FakeKeyguardRepository()
bouncerRepository = FakeKeyguardBouncerRepository()
@@ -174,22 +171,3 @@
assertThat(secureCameraActive()).isFalse()
}
}
-
-class FakeCommandQueue(val context: Context, val displayTracker: DisplayTracker) :
- CommandQueue(context, displayTracker) {
- private val callbacks = mutableListOf<Callbacks>()
-
- override fun addCallback(callback: Callbacks) {
- callbacks.add(callback)
- }
-
- override fun removeCallback(callback: Callbacks) {
- callbacks.remove(callback)
- }
-
- fun doForEachCallback(func: (callback: Callbacks) -> Unit) {
- callbacks.forEach { func(it) }
- }
-
- fun callbackCount(): Int = callbacks.size
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index aa54a1c..447b333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,6 +38,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -64,6 +66,8 @@
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private UiEventLogger mUiEventLogger;
@Captor
ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -87,7 +91,8 @@
mStatusBarService,
mAuthController,
mKeyguardUpdateMonitor,
- mKeyguardStateController
+ mKeyguardStateController,
+ mUiEventLogger
);
}
@@ -238,6 +243,62 @@
eq(SESSION_KEYGUARD), any(InstanceId.class));
}
+ @Test
+ public void uiEventLoggedOnEndSessionWhenDeviceStartsSleeping() throws RemoteException {
+ // GIVEN session tracker start
+ mSessionTracker.start();
+ captureKeyguardUpdateMonitorCallback();
+ captureKeyguardStateControllerCallback();
+
+ // GIVEN keyguard becomes visible (ie: from lockdown), so there's a valid keyguard
+ // session running
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mKeyguardStateCallback.onKeyguardShowingChanged();
+
+ // WHEN device starts going to sleep
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN UI event is logged
+ verify(mUiEventLogger).log(
+ eq(SessionTracker.SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP),
+ any(InstanceId.class));
+ }
+
+ @Test
+ public void noUiEventLoggedOnEndSessionWhenDeviceStartsSleepingWithoutStartSession()
+ throws RemoteException {
+ // GIVEN session tracker start without any valid sessions
+ mSessionTracker.start();
+ captureKeyguardUpdateMonitorCallback();
+
+ // WHEN device starts going to sleep when there was no started sessions
+ mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+ // THEN UI event is never logged
+ verify(mUiEventLogger, never()).log(
+ eq(SessionTracker.SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP),
+ any(InstanceId.class));
+ }
+
+ @Test
+ public void uiEventLoggedOnEndSessionWhenKeyguardGoingAway() throws RemoteException {
+ // GIVEN session tracker started w/o any sessions
+ mSessionTracker.start();
+ captureKeyguardUpdateMonitorCallback();
+ captureKeyguardStateControllerCallback();
+
+ // WHEN keyguard was showing and now it's not
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mKeyguardStateCallback.onKeyguardShowingChanged();
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ mKeyguardStateCallback.onKeyguardShowingChanged();
+
+ // THEN UI event is logged
+ verify(mUiEventLogger).log(
+ eq(SessionTracker.SessionUiEvent.KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY),
+ any(InstanceId.class));
+ }
+
void captureKeyguardUpdateMonitorCallback() {
verify(mKeyguardUpdateMonitor).registerCallback(
mKeyguardUpdateMonitorCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index acde887..19f9960 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -22,6 +22,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,6 +33,8 @@
import com.android.systemui.multishade.data.repository.MultiShadeRepository
import com.android.systemui.multishade.shared.model.ProxiedInputModel
import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -42,6 +46,10 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -57,9 +65,11 @@
private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var falsingManager: FalsingManagerFake
+ @Mock private lateinit var shadeController: ShadeController
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
testScope = TestScope()
motionEvents = mutableSetOf()
@@ -75,18 +85,23 @@
repository = repository,
inputProxy = inputProxy,
)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.DUAL_SHADE, true)
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
falsingManager = FalsingManagerFake()
+
underTest =
MultiShadeMotionEventInteractor(
applicationContext = context,
applicationScope = testScope.backgroundScope,
multiShadeInteractor = interactor,
+ featureFlags = featureFlags,
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = keyguardTransitionRepository,
),
falsingManager = falsingManager,
+ shadeController = shadeController,
)
}
@@ -96,6 +111,39 @@
}
@Test
+ fun listenForIsAnyShadeExpanded_expanded_makesWindowViewVisible() =
+ testScope.runTest {
+ whenever(shadeController.isKeyguard).thenReturn(false)
+ repository.setExpansion(ShadeId.LEFT, 0.1f)
+ val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+ assertThat(expanded).isTrue()
+
+ verify(shadeController).makeExpandedVisible(anyBoolean())
+ }
+
+ @Test
+ fun listenForIsAnyShadeExpanded_collapsed_makesWindowViewInvisible() =
+ testScope.runTest {
+ whenever(shadeController.isKeyguard).thenReturn(false)
+ repository.setForceCollapseAll(true)
+ val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+ assertThat(expanded).isFalse()
+
+ verify(shadeController).makeExpandedInvisible()
+ }
+
+ @Test
+ fun listenForIsAnyShadeExpanded_collapsedOnKeyguard_makesWindowViewVisible() =
+ testScope.runTest {
+ whenever(shadeController.isKeyguard).thenReturn(true)
+ repository.setForceCollapseAll(true)
+ val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+ assertThat(expanded).isFalse()
+
+ verify(shadeController).makeExpandedVisible(anyBoolean())
+ }
+
+ @Test
fun shouldIntercept_initialDown_returnsFalse() =
testScope.runTest {
assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse()
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 fbe089a..ba29ca5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -369,6 +369,39 @@
verifyZeroInteractions(context, bubbles, eventLogger)
}
+
+ @Test
+ fun showNoteTask_keyboardShortcut_shouldStartActivity() {
+ val expectedInfo =
+ NOTE_TASK_INFO.copy(
+ entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT,
+ isKeyguardLocked = true,
+ )
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+ whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo)
+
+ createNoteTaskController()
+ .showNoteTask(
+ entryPoint = expectedInfo.entryPoint!!,
+ )
+
+ val intentCaptor = argumentCaptor<Intent>()
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+ assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, true)).isFalse()
+ }
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
+ verify(eventLogger).logNoteTaskOpened(expectedInfo)
+ verifyZeroInteractions(bubbles)
+ }
// endregion
// region setNoteTaskShortcutEnabled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index cd67e8d..ec4daee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -98,14 +98,24 @@
// region handleSystemKey
@Test
fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
- createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
}
@Test
+ fun handleSystemKey_receiveKeyboardShortcut_shouldShowNoteTask() {
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_N, 0, KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON))
+
+ verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT)
+ }
+
+ @Test
fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
- createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_UNKNOWN))
verifyZeroInteractions(controller)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 6f54f62..f5a3bec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -111,8 +111,6 @@
MockitoAnnotations.initMocks(this);
mDeviceConfigProxyFake = new DeviceConfigProxyFake();
- mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, "true", false);
mSystemClock = new FakeSystemClock();
mMainExecutor = new FakeExecutor(mSystemClock);
mBackgroundExecutor = new FakeExecutor(mSystemClock);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 46af89e..9ca7a85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.settings.UserTracker;
import org.junit.After;
@@ -64,6 +65,8 @@
private QSHost mQSHost;
@Mock
private Context mMockContext;
+ @Mock
+ private CustomTileAddedRepository mCustomTileAddedRepository;
private HandlerThread mThread;
private Handler mHandler;
@@ -86,8 +89,9 @@
mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
+
mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
- mTileLifecycle);
+ mCustomTileAddedRepository, mTileLifecycle);
}
@After
@@ -98,28 +102,34 @@
@Test
public void testSetTileAddedIfNotAdded() {
- when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
+ .thenReturn(false);
mTileServiceManager.startLifecycleManagerAndAddTile();
- verify(mQSHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true);
+ verify(mCustomTileAddedRepository)
+ .setTileAdded(mComponentName, mUserTracker.getUserId(), true);
}
@Test
public void testNotSetTileAddedIfAdded() {
- when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true);
+ when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
+ .thenReturn(true);
mTileServiceManager.startLifecycleManagerAndAddTile();
- verify(mQSHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true));
+ verify(mCustomTileAddedRepository, never())
+ .setTileAdded(eq(mComponentName), anyInt(), eq(true));
}
@Test
public void testSetTileAddedCorrectUser() {
int user = 10;
when(mUserTracker.getUserId()).thenReturn(user);
- when(mQSHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false);
+ when(mCustomTileAddedRepository.isTileAdded(eq(mComponentName), anyInt()))
+ .thenReturn(false);
mTileServiceManager.startLifecycleManagerAndAddTile();
- verify(mQSHost).setTileAdded(mComponentName, user, true);
+ verify(mCustomTileAddedRepository)
+ .setTileAdded(mComponentName, user, true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index fb93367..12b5656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
@@ -98,6 +99,8 @@
private PanelInteractor mPanelInteractor;
@Captor
private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksArgumentCaptor;
+ @Mock
+ private CustomTileAddedRepository mCustomTileAddedRepository;
@Before
public void setUp() throws Exception {
@@ -115,7 +118,7 @@
mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController,
- mPanelInteractor);
+ mPanelInteractor, mCustomTileAddedRepository);
}
@After
@@ -293,9 +296,11 @@
TestTileServices(QSHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
KeyguardStateController keyguardStateController, CommandQueue commandQueue,
- StatusBarIconController statusBarIconController, PanelInteractor panelInteractor) {
+ StatusBarIconController statusBarIconController, PanelInteractor panelInteractor,
+ CustomTileAddedRepository customTileAddedRepository) {
super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
- commandQueue, statusBarIconController, panelInteractor);
+ commandQueue, statusBarIconController, panelInteractor,
+ customTileAddedRepository);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 0b9fbd9..59f0d96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -259,7 +259,6 @@
val securityController = FakeSecurityController()
val fgsManagerController =
FakeFgsManagerController(
- isAvailable = true,
showFooterDot = false,
numRunningPackages = 0,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
new file mode 100644
index 0000000..d7ab903
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: CustomTileAddedSharedPrefsRepository
+
+ @Test
+ fun setTileAdded_inSharedPreferences() {
+ val userId = 0
+ val sharedPrefs = FakeSharedPreferences()
+ val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+ underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+ underTest.setTileAdded(TEST_COMPONENT, userId, added = true)
+ assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isTrue()
+
+ underTest.setTileAdded(TEST_COMPONENT, userId, added = false)
+ assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isFalse()
+ }
+
+ @Test
+ fun setTileAdded_differentComponents() {
+ val userId = 0
+ val sharedPrefs = FakeSharedPreferences()
+ val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+ underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+ underTest.setTileAdded(TEST_COMPONENT, userId, added = true)
+
+ assertThat(sharedPrefs.getForComponentName(TEST_COMPONENT)).isTrue()
+ assertThat(sharedPrefs.getForComponentName(OTHER_TEST_COMPONENT)).isFalse()
+ }
+
+ @Test
+ fun setTileAdded_differentUsers() {
+ val sharedPrefs0 = FakeSharedPreferences()
+ val sharedPrefs1 = FakeSharedPreferences()
+ val userFileManager = FakeUserFileManager(mapOf(0 to sharedPrefs0, 1 to sharedPrefs1))
+
+ underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+ underTest.setTileAdded(TEST_COMPONENT, userId = 1, added = true)
+
+ assertThat(sharedPrefs0.getForComponentName(TEST_COMPONENT)).isFalse()
+ assertThat(sharedPrefs1.getForComponentName(TEST_COMPONENT)).isTrue()
+ }
+
+ @Test
+ fun isTileAdded_fromSharedPreferences() {
+ val userId = 0
+ val sharedPrefs = FakeSharedPreferences()
+ val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+ underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+ assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
+
+ sharedPrefs.setForComponentName(TEST_COMPONENT, true)
+ assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isTrue()
+
+ sharedPrefs.setForComponentName(TEST_COMPONENT, false)
+ assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
+ }
+
+ @Test
+ fun isTileAdded_differentComponents() {
+ val userId = 0
+ val sharedPrefs = FakeSharedPreferences()
+ val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs))
+
+ underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+ sharedPrefs.setForComponentName(OTHER_TEST_COMPONENT, true)
+
+ assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse()
+ assertThat(underTest.isTileAdded(OTHER_TEST_COMPONENT, userId)).isTrue()
+ }
+
+ @Test
+ fun isTileAdded_differentUsers() {
+ val sharedPrefs0 = FakeSharedPreferences()
+ val sharedPrefs1 = FakeSharedPreferences()
+ val userFileManager = FakeUserFileManager(mapOf(0 to sharedPrefs0, 1 to sharedPrefs1))
+
+ underTest = CustomTileAddedSharedPrefsRepository(userFileManager)
+
+ sharedPrefs1.setForComponentName(TEST_COMPONENT, true)
+
+ assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 0)).isFalse()
+ assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 1)).isTrue()
+ }
+
+ private fun SharedPreferences.getForComponentName(componentName: ComponentName): Boolean {
+ return getBoolean(componentName.flattenToString(), false)
+ }
+
+ private fun SharedPreferences.setForComponentName(
+ componentName: ComponentName,
+ value: Boolean
+ ) {
+ edit().putBoolean(componentName.flattenToString(), value).commit()
+ }
+
+ companion object {
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+ private val OTHER_TEST_COMPONENT = ComponentName("pkg", "other")
+ }
+}
+
+private const val FILE_NAME = "tiles_prefs"
+
+private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+ UserFileManager {
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences {
+ if (fileName != FILE_NAME) {
+ throw IllegalArgumentException("Preference files must be $FILE_NAME")
+ }
+ return sharedPrefs.getValue(userId)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index d3ec1dd..28aeba4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -317,6 +317,26 @@
}
@Test
+ fun testDisableByPolicyThenRemoved_changesColor() {
+ val stateActive = QSTile.State()
+ stateActive.state = Tile.STATE_ACTIVE
+
+ val stateDisabledByPolicy = stateActive.copy()
+ stateDisabledByPolicy.disabledByPolicy = true
+
+ tileView.changeState(stateActive)
+ val activeColors = tileView.getCurrentColors()
+
+ tileView.changeState(stateDisabledByPolicy)
+ // It has unavailable colors
+ assertThat(tileView.getCurrentColors()).isNotEqualTo(activeColors)
+
+ // When we get back to not disabled by policy tile, it should go back to active colors
+ tileView.changeState(stateActive)
+ assertThat(tileView.getCurrentColors()).containsExactlyElementsIn(activeColors)
+ }
+
+ @Test
fun testDisabledByPolicy_secondaryLabelText() {
val testA11yLabel = "TEST_LABEL"
context.orCreateTestableResources
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index eb7b481..8cb5d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -19,6 +19,7 @@
import android.content.ComponentName
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
+import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableContext
import android.testing.TestableLooper
@@ -30,6 +31,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.model.SysUiState
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
@@ -37,16 +39,17 @@
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.recents.IOverviewProxy
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_OFF
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_ON
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_TURNING_OFF
-import com.android.systemui.shared.system.QuickStepContract.SCREEN_STATE_TURNING_ON
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_STATE_MASK
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_ASLEEP
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_GOING_TO_SLEEP
+import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_WAKING
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.sysui.ShellInterface
import com.google.common.util.concurrent.MoreExecutors
import dagger.Lazy
@@ -60,6 +63,7 @@
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.intThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
@@ -75,8 +79,11 @@
private lateinit var subject: OverviewProxyService
private val dumpManager = DumpManager()
private val displayTracker = FakeDisplayTracker(mContext)
+ private val fakeSystemClock = FakeSystemClock()
private val sysUiState = SysUiState(displayTracker)
private val screenLifecycle = ScreenLifecycle(dumpManager)
+ private val wakefulnessLifecycle =
+ WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
@Mock private lateinit var overviewProxy: IOverviewProxy.Stub
@Mock private lateinit var packageManager: PackageManager
@@ -130,6 +137,7 @@
sysUiState,
userTracker,
screenLifecycle,
+ wakefulnessLifecycle,
uiEventLogger,
displayTracker,
sysuiUnlockAnimationController,
@@ -145,42 +153,48 @@
}
@Test
- fun `ScreenLifecycle - screenTurnedOn triggers SysUI state flag changes `() {
- screenLifecycle.dispatchScreenTurnedOn()
+ fun `WakefulnessLifecycle - dispatchFinishedWakingUp sets SysUI flag to AWAKE`() {
+ // WakefulnessLifecycle is initialized to AWAKE initially, and won't emit a noop.
+ wakefulnessLifecycle.dispatchFinishedGoingToSleep()
+ clearInvocations(overviewProxy)
+
+ wakefulnessLifecycle.dispatchFinishedWakingUp()
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_ON }
+ intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
)
}
@Test
- fun `ScreenLifecycle - screenTurningOn triggers SysUI state flag changes `() {
- screenLifecycle.dispatchScreenTurningOn()
+ fun `WakefulnessLifecycle - dispatchStartedWakingUp sets SysUI flag to WAKING`() {
+ wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN)
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_TURNING_ON }
+ intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
)
}
@Test
- fun `ScreenLifecycle - screenTurnedOff triggers SysUI state flag changes `() {
- screenLifecycle.dispatchScreenTurnedOff()
+ fun `WakefulnessLifecycle - dispatchFinishedGoingToSleep sets SysUI flag to ASLEEP`() {
+ wakefulnessLifecycle.dispatchFinishedGoingToSleep()
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_OFF }
+ intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
)
}
@Test
- fun `ScreenLifecycle - screenTurningOff triggers SysUI state flag changes `() {
- screenLifecycle.dispatchScreenTurningOff()
+ fun `WakefulnessLifecycle - dispatchStartedGoingToSleep sets SysUI flag to GOING_TO_SLEEP`() {
+ wakefulnessLifecycle.dispatchStartedGoingToSleep(
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON
+ )
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_SCREEN_STATE_MASK == SCREEN_STATE_TURNING_OFF }
+ intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 47d88a5..77f7426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -23,28 +23,21 @@
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.HARDWARE
import android.graphics.ColorSpace
-import android.graphics.Insets
-import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.os.UserHandle
import android.os.UserManager
import android.testing.AndroidTestingRunner
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_METADATA_REFACTOR
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -61,9 +54,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-private const val USER_ID = 1
-private const val TASK_ID = 11
-
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TakeScreenshotServiceTest : SysuiTestCase() {
@@ -123,9 +113,6 @@
.whenever(requestProcessor)
.processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
- // Flipped in selected test cases
- flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-
service.attach(
mContext,
/* thread = */ null,
@@ -158,39 +145,6 @@
service.handleRequest(request, { /* onSaved */}, callback)
verify(controller, times(1))
- .takeScreenshotFullscreen(
- eq(topComponent),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any()
- )
-
- assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
- val logEvent = eventLogger.get(0)
-
- assertEquals(
- "Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId,
- SCREENSHOT_REQUESTED_KEY_OTHER.id
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- eventLogger.get(0).packageName
- )
- }
-
- @Test
- fun takeScreenshotFullscreen_screenshotDataEnabled() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verify(controller, times(1))
.handleScreenshot(
eq(ScreenshotData.fromRequest(request)),
/* onSavedListener = */ any(),
@@ -213,53 +167,7 @@
}
@Test
- fun takeScreenshotProvidedImage() {
- val bounds = Rect(50, 50, 150, 150)
- val bitmap = makeHardwareBitmap(100, 100)
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
- .setTopComponent(topComponent)
- .setTaskId(TASK_ID)
- .setUserId(USER_ID)
- .setBitmap(bitmap)
- .setBoundsOnScreen(bounds)
- .setInsets(Insets.NONE)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verify(controller, times(1))
- .handleImageAsScreenshot(
- argThat { b -> b.equalsHardwareBitmap(bitmap) },
- eq(bounds),
- eq(Insets.NONE),
- eq(TASK_ID),
- eq(USER_ID),
- eq(topComponent),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any()
- )
-
- assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
- val logEvent = eventLogger.get(0)
-
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- logEvent.eventId,
- SCREENSHOT_REQUESTED_OVERVIEW.id
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- eventLogger.get(0).packageName
- )
- }
-
- @Test
fun takeScreenshotFullscreen_userLocked() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
whenever(userManager.isUserUnlocked).thenReturn(false)
val request =
@@ -300,8 +208,6 @@
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
.thenReturn(true)
@@ -350,143 +256,7 @@
}
@Test
- fun takeScreenshotFullscreen_userLocked_metadataDisabled() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, false)
- whenever(userManager.isUserUnlocked).thenReturn(false)
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
- verify(callback, times(1)).reportError()
- verifyZeroInteractions(controller)
-
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
- val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
- val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
- }
-
- @Test
- fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers_metadataDisabled() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-
- whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
- .thenReturn(true)
-
- whenever(
- devicePolicyResourcesManager.getString(
- eq(SCREENSHOT_BLOCKED_BY_ADMIN),
- /* Supplier<String> */
- any(),
- )
- )
- .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- // error shown: Toast.makeText(...).show(), untestable
- verify(callback, times(1)).reportError()
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
- val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
- val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
- }
-
- @Test
- fun takeScreenshot_workProfile_nullBitmap_metadataDisabled() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, false)
-
- val request =
- ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
- .setTopComponent(topComponent)
- .build()
-
- doThrow(IllegalStateException::class.java)
- .whenever(requestProcessor)
- .processAsync(any(ScreenshotRequest::class.java), any())
-
- service.handleRequest(request, { /* onSaved */}, callback)
-
- verify(callback, times(1)).reportError()
- verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
- verifyZeroInteractions(controller)
- assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
- val requestEvent = eventLogger.get(0)
- assertEquals(
- "Expected SCREENSHOT_REQUESTED_* UiEvent",
- SCREENSHOT_REQUESTED_KEY_OTHER.id,
- requestEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- requestEvent.packageName
- )
- val failureEvent = eventLogger.get(1)
- assertEquals(
- "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
- SCREENSHOT_CAPTURE_FAILED.id,
- failureEvent.eventId
- )
- assertEquals(
- "Expected supplied package name",
- topComponent.packageName,
- failureEvent.packageName
- )
- }
- @Test
fun takeScreenshot_workProfile_nullBitmap() {
- flags.set(SCREENSHOT_METADATA_REFACTOR, true)
-
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
.setTopComponent(topComponent)
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 1bd13aa..7b37ea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -103,6 +103,7 @@
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
@@ -286,6 +287,7 @@
@Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
@Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ @Mock protected MultiShadeInteractor mMultiShadeInteractor;
@Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock protected MotionEvent mDownMotionEvent;
@@ -570,6 +572,7 @@
mLockscreenToOccludedTransitionViewModel,
mMainDispatcher,
mKeyguardTransitionInteractor,
+ () -> mMultiShadeInteractor,
mDumpManager,
mKeyuardLongPressViewModel,
mKeyguardInteractor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2a10823..bc8ab1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -86,6 +86,7 @@
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+ @Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -173,11 +174,13 @@
applicationContext = context,
applicationScope = testScope.backgroundScope,
multiShadeInteractor = multiShadeInteractor,
+ featureFlags = featureFlags,
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = FakeKeyguardTransitionRepository(),
),
falsingManager = FalsingManagerFake(),
+ shadeController = shadeController,
)
},
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 86660a4..56385b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -185,11 +185,13 @@
applicationContext = context,
applicationScope = testScope.backgroundScope,
multiShadeInteractor = multiShadeInteractor,
+ featureFlags = featureFlags,
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = FakeKeyguardTransitionRepository(),
),
falsingManager = FalsingManagerFake(),
+ shadeController = shadeController,
)
},
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 374aae1..78f5bf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -122,7 +122,7 @@
isEnabled = true,
handleAllUsers = true,
defaultClockProvider = fakeDefaultProvider,
- keepAllLoaded = true,
+ keepAllLoaded = false,
subTag = "Test",
) {
override fun querySettings() { }
@@ -154,8 +154,8 @@
pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle)
val list = registry.getClocks()
assertEquals(
- list,
- listOf(
+ list.toSet(),
+ setOf(
ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
ClockMetadata("clock_1", "clock 1"),
ClockMetadata("clock_2", "clock 2"),
@@ -187,8 +187,8 @@
pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
val list = registry.getClocks()
assertEquals(
- list,
- listOf(
+ list.toSet(),
+ setOf(
ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
ClockMetadata("clock_1", "clock 1"),
ClockMetadata("clock_2", "clock 2")
@@ -293,6 +293,53 @@
assertEquals(4, listChangeCallCount)
}
+ @Test
+ fun pluginAddRemove_concurrentModification() {
+ val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ val mockPluginLifecycle3 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ val mockPluginLifecycle4 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ val plugin1 = FakeClockPlugin().addClock("clock_1", "clock 1")
+ val plugin2 = FakeClockPlugin().addClock("clock_2", "clock 2")
+ val plugin3 = FakeClockPlugin().addClock("clock_3", "clock 3")
+ val plugin4 = FakeClockPlugin().addClock("clock_4", "clock 4")
+ whenever(mockPluginLifecycle1.isLoaded).thenReturn(true)
+ whenever(mockPluginLifecycle2.isLoaded).thenReturn(true)
+ whenever(mockPluginLifecycle3.isLoaded).thenReturn(true)
+ whenever(mockPluginLifecycle4.isLoaded).thenReturn(true)
+
+ // Set the current clock to the final clock to load
+ registry.applySettings(ClockSettings("clock_4", null))
+ scheduler.runCurrent()
+
+ // When ClockRegistry attempts to unload a plugin, we at that point decide to load and
+ // unload other plugins. This causes ClockRegistry to modify the list of available clock
+ // plugins while it is being iterated over. In production this happens as a result of a
+ // thread race, instead of synchronously like it does here.
+ whenever(mockPluginLifecycle2.unloadPlugin()).then {
+ pluginListener.onPluginDetached(mockPluginLifecycle1)
+ pluginListener.onPluginLoaded(plugin4, mockContext, mockPluginLifecycle4)
+ }
+
+ // Load initial plugins
+ pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1)
+ pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2)
+ pluginListener.onPluginLoaded(plugin3, mockContext, mockPluginLifecycle3)
+
+ // Repeatedly verify the loaded providers to get final state
+ registry.verifyLoadedProviders()
+ scheduler.runCurrent()
+ registry.verifyLoadedProviders()
+ scheduler.runCurrent()
+
+ // Verify all plugins were correctly loaded into the registry
+ assertEquals(registry.getClocks().toSet(), setOf(
+ ClockMetadata("DEFAULT", "Default Clock"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ ))
+ }
@Test
fun jsonDeserialization_gotExpectedObject() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index f581154..f4cd383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,6 +33,7 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
+import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
@@ -397,9 +398,10 @@
@Test
public void testHandleSysKey() {
- mCommandQueue.handleSystemKey(1);
+ KeyEvent testEvent = new KeyEvent(1, 1);
+ mCommandQueue.handleSystemKey(testEvent);
waitForIdleSync();
- verify(mCallbacks).handleSystemKey(eq(1));
+ verify(mCallbacks).handleSystemKey(eq(testEvent));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index 39ea46a..e2aef31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -126,7 +126,7 @@
}
fun assertLastProgress(progress: Float) {
- waitForCondition { progress == progressHistory.last() }
+ waitForCondition { progress == progressHistory.lastOrNull() }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index d8b3270..65735f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,7 +21,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.flowOf
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
@@ -39,12 +38,17 @@
private val _isStrongBiometricAllowed = MutableStateFlow(false)
override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
+ private val _isNonStrongBiometricAllowed = MutableStateFlow(false)
+ override val isNonStrongBiometricAllowed: StateFlow<Boolean>
+ get() = _isNonStrongBiometricAllowed
+
private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false)
override val isFingerprintEnabledByDevicePolicy =
_isFingerprintEnabledByDevicePolicy.asStateFlow()
+ private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false)
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
- get() = flowOf(true)
+ get() = _isFaceAuthSupportedInCurrentPosture
private val _isCurrentUserInLockdown = MutableStateFlow(false)
override val isCurrentUserInLockdown: Flow<Boolean>
@@ -66,7 +70,19 @@
_isFaceEnrolled.value = isFaceEnrolled
}
+ fun setIsFaceAuthSupportedInCurrentPosture(value: Boolean) {
+ _isFaceAuthSupportedInCurrentPosture.value = value
+ }
+
fun setIsFaceAuthEnabled(enabled: Boolean) {
_isFaceAuthEnabled.value = enabled
}
+
+ fun setIsUserInLockdown(value: Boolean) {
+ _isCurrentUserInLockdown.value = value
+ }
+
+ fun setIsNonStrongBiometricAllowed(value: Boolean) {
+ _isNonStrongBiometricAllowed.value = value
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
new file mode 100644
index 0000000..fe94117
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.content.Context
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.CommandQueue
+import org.mockito.Mockito.mock
+
+class FakeCommandQueue : CommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java)) {
+ private val callbacks = mutableListOf<Callbacks>()
+
+ override fun addCallback(callback: Callbacks) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: Callbacks) {
+ callbacks.remove(callback)
+ }
+
+ fun doForEachCallback(func: (callback: Callbacks) -> Unit) {
+ callbacks.forEach { func(it) }
+ }
+
+ fun callbackCount(): Int = callbacks.size
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 00b1a40..4bfd3d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -30,10 +30,19 @@
override val isRunning: Flow<Boolean>
get() = _isRunning
- override val availableFpSensorType: BiometricType?
- get() = null
+ private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+ override val availableFpSensorType: Flow<BiometricType?>
+ get() = fpSensorType
fun setLockedOut(lockedOut: Boolean) {
_isLockedOut.value = lockedOut
}
+
+ fun setIsRunning(value: Boolean) {
+ _isRunning.value = value
+ }
+
+ fun setAvailableFpSensorType(value: BiometricType?) {
+ fpSensorType.value = value
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
index 1dda472..8a6d2aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -48,8 +48,6 @@
override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
- override val bouncerPromptReason = 0
- override val bouncerErrorMessage: CharSequence? = null
private val _isAlternateBouncerVisible = MutableStateFlow(false)
override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
override var lastAlternateBouncerVisibleTime: Long = 0L
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 194ed02..d411590 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -129,6 +129,10 @@
_isKeyguardShowing.value = isShowing
}
+ fun setKeyguardGoingAway(isGoingAway: Boolean) {
+ _isKeyguardGoingAway.value = isGoingAway
+ }
+
fun setKeyguardOccluded(isOccluded: Boolean) {
_isKeyguardOccluded.value = isOccluded
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
new file mode 100644
index 0000000..6690de8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeTrustRepository : TrustRepository {
+ private val _isCurrentUserTrusted = MutableStateFlow(false)
+ override val isCurrentUserTrusted: Flow<Boolean>
+ get() = _isCurrentUserTrusted
+
+ fun setCurrentUserTrusted(trust: Boolean) {
+ _isCurrentUserTrusted.value = trust
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index ced7955..9ff7dd5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -23,11 +23,9 @@
/** A fake [FgsManagerController] to be used in tests. */
class FakeFgsManagerController(
- isAvailable: Boolean = true,
showFooterDot: Boolean = false,
numRunningPackages: Int = 0,
) : FgsManagerController {
- override val isAvailable: MutableStateFlow<Boolean> = MutableStateFlow(isAvailable)
override var numRunningPackages = numRunningPackages
set(value) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 53bb340..fbc2381 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -100,4 +100,8 @@
fun setGuestUserAutoCreated(value: Boolean) {
_isGuestUserAutoCreated = value
}
+
+ fun setUserSwitching(value: Boolean) {
+ _userSwitchingInProgress.value = value
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e159f18..7463061 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -271,6 +271,14 @@
*/
void onClientChangeLocked(boolean serviceInfoChanged);
+ /**
+ * Called back to notify the system the proxy client for a device has changed.
+ *
+ * Changes include if the proxy is unregistered, if its service info list has changed, or if
+ * its focus appearance has changed.
+ */
+ void onProxyChanged(int deviceId);
+
int getCurrentUserIdLocked();
Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
@@ -315,8 +323,6 @@
void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
- void setCurrentUserFocusAppearance(int strokeWidth, int color);
-
}
public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8473ab7..ae4e531 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -25,6 +25,9 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -189,7 +192,7 @@
AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager,
- SystemActionPerformer.SystemActionsChangedListener {
+ SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{
private static final boolean DEBUG = false;
@@ -471,7 +474,8 @@
new MagnificationScaleProvider(mContext));
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
- mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext);
+ mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
+ mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
init();
}
@@ -876,6 +880,19 @@
};
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
Context.RECEIVER_EXPORTED);
+
+ final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int deviceId = intent.getIntExtra(
+ EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
+ mProxyManager.clearConnections(deviceId);
+ }
+ };
+
+ final IntentFilter virtualDeviceFilter = new IntentFilter(ACTION_VIRTUAL_DEVICE_REMOVED);
+ mContext.registerReceiver(virtualDeviceReceiver, virtualDeviceFilter,
+ Context.RECEIVER_NOT_EXPORTED);
}
/**
@@ -954,21 +971,42 @@
final int resolvedUserId = mSecurityPolicy
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
+ // Support a process moving from the default device to a single virtual
+ // device.
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ Client client = new Client(callback, Binder.getCallingUid(), userState, deviceId);
// If the client is from a process that runs across users such as
// the system UI or the system we add it to the global state that
// is shared across users.
- AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
- Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Added global client for proxy-ed pid: "
+ + Binder.getCallingPid() + " for device id " + deviceId
+ + " with package names " + Arrays.toString(client.mPackageNames));
+ }
+ return IntPair.of(mProxyManager.getStateLocked(deviceId,
+ mUiAutomationManager.isUiAutomationRunningLocked()),
+ client.mLastSentRelevantEventTypes);
+ }
mGlobalClients.register(callback, client);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
- return IntPair.of(
- combineUserStateAndProxyState(getClientStateLocked(userState),
- mProxyManager.getStateLocked()),
- client.mLastSentRelevantEventTypes);
} else {
+ // If the display belongs to a proxy connections
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Added user client for proxy-ed pid: "
+ + Binder.getCallingPid() + " for device id " + deviceId
+ + " with package names " + Arrays.toString(client.mPackageNames));
+ }
+ return IntPair.of(mProxyManager.getStateLocked(deviceId,
+ mUiAutomationManager.isUiAutomationRunningLocked()),
+ client.mLastSentRelevantEventTypes);
+ }
userState.mUserClients.register(callback, client);
// If this client is not for the current user we do not
// return a state since it is not for the foreground user.
@@ -977,12 +1015,10 @@
Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid()
+ " and userId:" + mCurrentUserId);
}
- return IntPair.of(
- (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
- getClientStateLocked(userState), mProxyManager.getStateLocked())
- : 0,
- client.mLastSentRelevantEventTypes);
}
+ return IntPair.of(
+ (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+ client.mLastSentRelevantEventTypes);
}
}
@@ -1108,7 +1144,7 @@
}
private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
- if (mProxyManager.isProxyed(event.getDisplayId())) {
+ if (mProxyManager.isProxyedDisplay(event.getDisplayId())) {
mProxyManager.sendAccessibilityEventLocked(event);
} else {
notifyAccessibilityServicesDelayedLocked(event, false);
@@ -1174,6 +1210,12 @@
final int resolvedUserId;
final List<AccessibilityServiceInfo> serviceInfos;
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getInstalledAndEnabledServiceInfosLocked(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK, deviceId);
+ }
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
@@ -1209,6 +1251,12 @@
}
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getInstalledAndEnabledServiceInfosLocked(feedbackType,
+ deviceId);
+ }
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
@@ -1253,19 +1301,25 @@
if (resolvedUserId != mCurrentUserId) {
return;
}
- List<AccessibilityServiceConnection> services =
- getUserStateLocked(resolvedUserId).mBoundServices;
- int numServices = services.size() + mProxyManager.getNumProxysLocked();
- interfacesToInterrupt = new ArrayList<>(numServices);
- for (int i = 0; i < services.size(); i++) {
- AccessibilityServiceConnection service = services.get(i);
- IBinder a11yServiceBinder = service.mService;
- IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
- if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
- interfacesToInterrupt.add(a11yServiceInterface);
+
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ interfacesToInterrupt = new ArrayList<>();
+ mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt, deviceId);
+ } else {
+ List<AccessibilityServiceConnection> services =
+ getUserStateLocked(resolvedUserId).mBoundServices;
+ interfacesToInterrupt = new ArrayList<>(services.size());
+ for (int i = 0; i < services.size(); i++) {
+ AccessibilityServiceConnection service = services.get(i);
+ IBinder a11yServiceBinder = service.mService;
+ IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+ if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
+ interfacesToInterrupt.add(a11yServiceInterface);
+ }
}
}
- mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
}
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
try {
@@ -1841,7 +1895,8 @@
return result;
}
- private void notifyClearAccessibilityCacheLocked() {
+ @Override
+ public void notifyClearAccessibilityCacheLocked() {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
@@ -2045,18 +2100,15 @@
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
int relevantEventTypes;
- boolean changed = false;
synchronized (mLock) {
relevantEventTypes = computeRelevantEventTypesLocked(userState, client);
-
- if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
- client.mLastSentRelevantEventTypes = relevantEventTypes;
- changed = true;
+ if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+ if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+ client.mLastSentRelevantEventTypes = relevantEventTypes;
+ client.mCallback.setRelevantEventTypes(relevantEventTypes);
+ }
}
}
- if (changed) {
- client.mCallback.setRelevantEventTypes(relevantEventTypes);
- }
}));
});
}
@@ -2076,7 +2128,6 @@
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
: 0;
- relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
return relevantEventTypes;
}
@@ -2130,7 +2181,7 @@
}
}
- private static boolean isClientInPackageAllowlist(
+ static boolean isClientInPackageAllowlist(
@Nullable AccessibilityServiceInfo serviceInfo, Client client) {
if (serviceInfo == null) return false;
@@ -2323,24 +2374,20 @@
updateAccessibilityEnabledSettingLocked(userState);
}
- private int combineUserStateAndProxyState(int userState, int proxyState) {
- return userState | proxyState;
+ void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ scheduleUpdateClientsIfNeededLocked(userState, false);
}
- void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState,
+ boolean forceUpdate) {
final int clientState = getClientStateLocked(userState);
- final int proxyState = mProxyManager.getStateLocked();
- if ((userState.getLastSentClientStateLocked() != clientState
- || mProxyManager.getLastSentStateLocked() != proxyState)
+ if (((userState.getLastSentClientStateLocked() != clientState || forceUpdate))
&& (mGlobalClients.getRegisteredCallbackCount() > 0
|| userState.mUserClients.getRegisteredCallbackCount() > 0)) {
userState.setLastSentClientStateLocked(clientState);
- mProxyManager.setLastStateLocked(proxyState);
- // Send both the user and proxy state to the app for now.
- // TODO(b/250929565): Send proxy state to proxy clients
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendStateToAllClients,
- this, combineUserStateAndProxyState(clientState, proxyState),
+ this, clientState,
userState.mUserId));
}
}
@@ -2360,8 +2407,13 @@
mTraceManager.logTrace(LOG_TAG + ".sendStateToClients",
FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState);
}
- clients.broadcast(ignoreRemoteException(
- client -> client.setState(clientState)));
+ clients.broadcastForEachCookie(ignoreRemoteException(
+ client -> {
+ Client managerClient = ((Client) client);
+ if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+ managerClient.mCallback.setState(clientState);
+ }
+ }));
}
private void scheduleNotifyClientsOfServicesStateChangeLocked(
@@ -2384,8 +2436,14 @@
mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange",
FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout);
}
- clients.broadcast(ignoreRemoteException(
- client -> client.notifyServicesStateChanged(uiTimeout)));
+
+ clients.broadcastForEachCookie(ignoreRemoteException(
+ client -> {
+ Client managerClient = ((Client) client);
+ if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+ managerClient.mCallback.notifyServicesStateChanged(uiTimeout);
+ }
+ }));
}
private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
@@ -2458,7 +2516,6 @@
}
inputFilter = mInputFilter;
setInputFilter = true;
- mProxyManager.setAccessibilityInputFilter(mInputFilter);
}
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
mInputFilter.setCombinedGenericMotionEventSources(
@@ -2491,6 +2548,7 @@
"inputFilter=" + inputFilter);
}
mWindowManagerService.setInputFilter(inputFilter);
+ mProxyManager.setAccessibilityInputFilter(inputFilter);
}
}
@@ -2555,6 +2613,20 @@
* @param userState the new user state
*/
private void onUserStateChangedLocked(AccessibilityUserState userState) {
+ onUserStateChangedLocked(userState, false);
+ }
+
+ /**
+ * Called when any property of the user state has changed.
+ *
+ * @param userState the new user state
+ * @param forceUpdate whether to force an update of the app Clients.
+ */
+ private void onUserStateChangedLocked(AccessibilityUserState userState, boolean forceUpdate) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "onUserStateChangedLocked for user " + userState.mUserId + " with "
+ + "forceUpdate: " + forceUpdate);
+ }
// TODO: Remove this hack
mInitialized = true;
updateLegacyCapabilitiesLocked(userState);
@@ -2567,7 +2639,7 @@
scheduleUpdateFingerprintGestureHandling(userState);
scheduleUpdateInputFilter(userState);
updateRelevantEventsLocked(userState);
- scheduleUpdateClientsIfNeededLocked(userState);
+ scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
updateAccessibilityShortcutKeyTargetsLocked(userState);
updateAccessibilityButtonTargetsLocked(userState);
// Update the capabilities before the mode because we will check the current mode is
@@ -2614,7 +2686,7 @@
if (display != null) {
if (observingWindows) {
mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
- mProxyManager.isProxyed(display.getDisplayId()));
+ mProxyManager.isProxyedDisplay(display.getDisplayId()));
} else {
mA11yWindowManager.stopTrackingWindows(display.getDisplayId());
}
@@ -2881,6 +2953,8 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
userState.mUserId);
+
+ mProxyManager.updateTimeoutsIfNeeded(nonInteractiveUiTimeout, interactiveUiTimeout);
if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
|| interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
@@ -3646,8 +3720,14 @@
}
synchronized(mLock) {
- final AccessibilityUserState userState = getCurrentUserStateLocked();
- return getRecommendedTimeoutMillisLocked(userState);
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getRecommendedTimeoutMillisLocked(deviceId);
+ } else {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ return getRecommendedTimeoutMillisLocked(userState);
+ }
}
}
@@ -3726,6 +3806,11 @@
mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
}
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getFocusStrokeWidthLocked(deviceId);
+ }
final AccessibilityUserState userState = getCurrentUserStateLocked();
return userState.getFocusStrokeWidthLocked();
@@ -3742,6 +3827,11 @@
mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
}
synchronized (mLock) {
+ final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+ Binder.getCallingUid());
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ return mProxyManager.getFocusColorLocked(deviceId);
+ }
final AccessibilityUserState userState = getCurrentUserStateLocked();
return userState.getFocusColorLocked();
@@ -3828,9 +3918,9 @@
throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
+ " not tracked by accessibility.");
}
- if (mProxyManager.isProxyed(displayId)) {
+ if (mProxyManager.isProxyedDisplay(displayId)) {
throw new IllegalArgumentException("The display " + displayId + " is already being"
- + "proxy-ed");
+ + " proxy-ed");
}
final long identity = Binder.clearCallingIdentity();
@@ -3861,7 +3951,7 @@
}
boolean isDisplayProxyed(int displayId) {
- return mProxyManager.isProxyed(displayId);
+ return mProxyManager.isProxyedDisplay(displayId);
}
@Override
@@ -4009,13 +4099,71 @@
@Override
public void onClientChangeLocked(boolean serviceInfoChanged) {
+ onClientChangeLocked(serviceInfoChanged, false);
+ }
+
+ /**
+ * Called when the state of a service or proxy has changed
+ * @param serviceInfoChanged if the service info has changed
+ * @param forceUpdate whether to force an update of state for app clients
+ */
+ public void onClientChangeLocked(boolean serviceInfoChanged, boolean forceUpdate) {
AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
- onUserStateChangedLocked(userState);
+ onUserStateChangedLocked(userState, forceUpdate);
if (serviceInfoChanged) {
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
}
+
+ @Override
+ public void onProxyChanged(int deviceId) {
+ mProxyManager.onProxyChanged(deviceId);
+ }
+
+ /**
+ * Removes the device from tracking. This will reset any AccessibilityManagerClients to be
+ * associated with the default user id.
+ */
+ @Override
+ public void removeDeviceIdLocked(int deviceId) {
+ resetClientsLocked(deviceId, getCurrentUserStateLocked().mUserClients);
+ resetClientsLocked(deviceId, mGlobalClients);
+ // Force an update of A11yManagers if the state was previously a proxy state and needs to be
+ // returned to the default device state.
+ onClientChangeLocked(true, true);
+ }
+
+ private void resetClientsLocked(int deviceId,
+ RemoteCallbackList<IAccessibilityManagerClient> clients) {
+ if (clients == null || clients.getRegisteredCallbackCount() == 0) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+ final Client appClient = ((Client) clients.getRegisteredCallbackCookie(i));
+ if (appClient.mDeviceId == deviceId) {
+ appClient.mDeviceId = DEVICE_ID_DEFAULT;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void updateWindowsForAccessibilityCallbackLocked() {
+ updateWindowsForAccessibilityCallbackLocked(getUserStateLocked(mCurrentUserId));
+ }
+
+ @Override
+ public RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked() {
+ return mGlobalClients;
+ }
+
+ @Override
+ public RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked() {
+ return getCurrentUserState().mUserClients;
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -4327,13 +4475,22 @@
final IAccessibilityManagerClient mCallback;
final String[] mPackageNames;
int mLastSentRelevantEventTypes;
+ int mUid;
+ int mDeviceId = DEVICE_ID_DEFAULT;
private Client(IAccessibilityManagerClient callback, int clientUid,
- AccessibilityUserState userState) {
+ AccessibilityUserState userState, int deviceId) {
mCallback = callback;
mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+ mUid = clientUid;
+ mDeviceId = deviceId;
synchronized (mLock) {
- mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+ if (mProxyManager.isProxyedDeviceId(deviceId)) {
+ mLastSentRelevantEventTypes =
+ mProxyManager.computeRelevantEventTypesLocked(this);
+ } else {
+ mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+ }
}
}
}
@@ -4819,8 +4976,10 @@
}
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
- client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
- userState.getFocusColorLocked());
+ if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+ client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
+ userState.getFocusColorLocked());
+ }
}));
});
@@ -5055,11 +5214,4 @@
transaction.apply();
transaction.close();
}
-
- @Override
- public void setCurrentUserFocusAppearance(int strokeWidth, int color) {
- synchronized (mLock) {
- getCurrentUserStateLocked().setFocusAppearanceLocked(strokeWidth, color);
- }
- }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index b19a502..ab01fc3 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -67,6 +67,7 @@
public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
+ private int mDeviceId;
private int mDisplayId;
private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
@@ -75,6 +76,9 @@
/** The color of the focus rectangle */
private int mFocusColor;
+ private int mInteractiveTimeout;
+ private int mNonInteractiveTimeout;
+
ProxyAccessibilityServiceConnection(
Context context,
ComponentName componentName,
@@ -83,7 +87,7 @@
AccessibilitySecurityPolicy securityPolicy,
SystemSupport systemSupport, AccessibilityTrace trace,
WindowManagerInternal windowManagerInternal,
- AccessibilityWindowManager awm, int displayId) {
+ AccessibilityWindowManager awm, int displayId, int deviceId) {
super(/* userState= */null, context, componentName, accessibilityServiceInfo, id,
mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal,
/* systemActionPerformer= */ null, awm, /* activityTaskManagerService= */ null);
@@ -93,6 +97,14 @@
R.dimen.accessibility_focus_highlight_stroke_width);
mFocusColor = mContext.getResources().getColor(
R.color.accessibility_focus_highlight_color);
+ mDeviceId = deviceId;
+ }
+
+ int getDisplayId() {
+ return mDisplayId;
+ }
+ int getDeviceId() {
+ return mDeviceId;
}
/**
@@ -155,6 +167,8 @@
proxyInfo.setAccessibilityTool(isAccessibilityTool);
proxyInfo.setInteractiveUiTimeoutMillis(interactiveUiTimeout);
proxyInfo.setNonInteractiveUiTimeoutMillis(nonInteractiveUiTimeout);
+ mInteractiveTimeout = interactiveUiTimeout;
+ mNonInteractiveTimeout = nonInteractiveUiTimeout;
// If any one service info doesn't set package names, i.e. if it's interested in all
// apps, the proxy shouldn't filter by package name even if some infos specify this.
@@ -167,7 +181,7 @@
// Update connection with mAccessibilityServiceInfo values.
setDynamicallyConfigurableProperties(proxyInfo);
// Notify manager service.
- mSystemSupport.onClientChangeLocked(true);
+ mSystemSupport.onProxyChanged(mDeviceId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -235,12 +249,7 @@
mFocusStrokeWidth = strokeWidth;
mFocusColor = color;
- // Sets the appearance data in the A11yUserState for now, since the A11yManagers are not
- // separated.
- // TODO(254545943): Separate proxy and non-proxy states so the focus appearance on the
- // phone is not affected by the appearance of a proxy-ed app.
- mSystemSupport.setCurrentUserFocusAppearance(mFocusStrokeWidth, mFocusColor);
- mSystemSupport.onClientChangeLocked(false);
+ mSystemSupport.onProxyChanged(mDeviceId);
}
}
@@ -572,17 +581,51 @@
throw new UnsupportedOperationException("setAnimationScale is not supported");
}
+ public int getInteractiveTimeout() {
+ return mInteractiveTimeout;
+ }
+
+ public int getNonInteractiveTimeout() {
+ return mNonInteractiveTimeout;
+ }
+
+ /**
+ * Returns true if a timeout was updated.
+ */
+ public boolean updateTimeouts(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+ final int newInteractiveUiTimeout = interactiveUiTimeout != 0
+ ? interactiveUiTimeout
+ : mAccessibilityServiceInfo.getInteractiveUiTimeoutMillis();
+ final int newNonInteractiveUiTimeout = nonInteractiveUiTimeout != 0
+ ? nonInteractiveUiTimeout
+ : mAccessibilityServiceInfo.getNonInteractiveUiTimeoutMillis();
+ boolean updated = false;
+
+ if (mInteractiveTimeout != newInteractiveUiTimeout) {
+ mInteractiveTimeout = newInteractiveUiTimeout;
+ updated = true;
+ }
+ if (mNonInteractiveTimeout != newNonInteractiveUiTimeout) {
+ mNonInteractiveTimeout = newNonInteractiveUiTimeout;
+ updated = true;
+ }
+ return updated;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
pw.append("Proxy[displayId=" + mDisplayId);
+ pw.append(", deviceId=" + mDeviceId);
pw.append(", feedbackType"
+ AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
pw.append(", eventTypes="
+ AccessibilityEvent.eventTypeToString(mEventTypes));
pw.append(", notificationTimeout=" + mNotificationTimeout);
+ pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveTimeout));
+ pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveTimeout));
pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth));
pw.append(", focusColor=").append(String.valueOf(mFocusColor));
pw.append(", installedAndEnabledServiceCount=").append(String.valueOf(
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index e258de1..d417197 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,26 +14,45 @@
* limitations under the License.
*/
package com.android.server.accessibility;
+
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
+
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.NonNull;
+import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+import com.android.internal.util.IntPair;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
/**
* Manages proxy connections.
@@ -53,26 +72,72 @@
static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
+ // AMS#mLock
private final Object mLock;
private final Context mContext;
+ private final Handler mMainHandler;
- // Used to determine if we should notify AccessibilityManager clients of updates.
- // TODO(254545943): Separate this so each display id has its own state. Currently there is no
- // way to identify from AccessibilityManager which proxy state should be returned.
- private int mLastState = -1;
+ private final UiAutomationManager mUiAutomationManager;
- private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+ // Device Id -> state. Used to determine if we should notify AccessibilityManager clients of
+ // updates.
+ private final SparseIntArray mLastStates = new SparseIntArray();
+
+ // Each display id entry in a SparseArray represents a proxy a11y user.
+ private final SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
new SparseArray<>();
- private AccessibilityWindowManager mA11yWindowManager;
+ private final AccessibilityWindowManager mA11yWindowManager;
private AccessibilityInputFilter mA11yInputFilter;
- ProxyManager(Object lock, AccessibilityWindowManager awm, Context context) {
+ private VirtualDeviceManagerInternal mLocalVdm;
+
+ private final SystemSupport mSystemSupport;
+
+ /**
+ * Callbacks into AccessibilityManagerService.
+ */
+ public interface SystemSupport {
+ /**
+ * Removes the device id from tracking.
+ */
+ void removeDeviceIdLocked(int deviceId);
+
+ /**
+ * Updates the windows tracking for the current user.
+ */
+ void updateWindowsForAccessibilityCallbackLocked();
+
+ /**
+ * Clears all caches.
+ */
+ void notifyClearAccessibilityCacheLocked();
+
+ /**
+ * Gets the clients for all users.
+ */
+ @NonNull
+ RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked();
+
+ /**
+ * Gets the clients for the current user.
+ */
+ @NonNull
+ RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked();
+ }
+
+ ProxyManager(Object lock, AccessibilityWindowManager awm,
+ Context context, Handler mainHandler, UiAutomationManager uiAutomationManager,
+ SystemSupport systemSupport) {
mLock = lock;
mA11yWindowManager = awm;
mContext = context;
+ mMainHandler = mainHandler;
+ mUiAutomationManager = uiAutomationManager;
+ mSystemSupport = systemSupport;
+ mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
}
/**
@@ -89,6 +154,12 @@
Slog.v(LOG_TAG, "Register proxy for display id: " + displayId);
}
+ VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+ if (vdm == null) {
+ return;
+ }
+ final int deviceId = vdm.getDeviceIdForDisplayId(displayId);
+
// Set a default AccessibilityServiceInfo that is used before the proxy's info is
// populated. A proxy has the touch exploration and window capabilities.
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -101,7 +172,7 @@
new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
id, mainHandler, mLock, securityPolicy, systemSupport, trace,
windowManagerInternal,
- mA11yWindowManager, displayId);
+ mA11yWindowManager, displayId, deviceId);
synchronized (mLock) {
mProxyA11yServiceConnections.put(displayId, connection);
@@ -113,20 +184,16 @@
@Override
public void binderDied() {
client.asBinder().unlinkToDeath(this, 0);
- clearConnection(displayId);
+ clearConnectionAndUpdateState(displayId);
}
};
client.asBinder().linkToDeath(deathRecipient, 0);
- // Notify apps that the service state has changed.
- // A11yManager#A11yServicesStateChangeListener
- synchronized (mLock) {
- connection.mSystemSupport.onClientChangeLocked(true);
- }
-
- if (mA11yInputFilter != null) {
- mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
- }
+ mMainHandler.post(() -> {
+ if (mA11yInputFilter != null) {
+ mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
+ }
+ });
connection.initializeServiceInterface(client);
}
@@ -134,38 +201,101 @@
* Unregister the proxy based on display id.
*/
public boolean unregisterProxy(int displayId) {
- return clearConnection(displayId);
- }
-
- private boolean clearConnection(int displayId) {
- boolean removed = false;
- synchronized (mLock) {
- if (mProxyA11yServiceConnections.contains(displayId)) {
- mProxyA11yServiceConnections.remove(displayId);
- removed = true;
- if (DEBUG) {
- Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId);
- }
- }
- }
- if (removed) {
- mA11yWindowManager.stopTrackingDisplayProxy(displayId);
- if (mA11yInputFilter != null) {
- final DisplayManager displayManager = (DisplayManager)
- mContext.getSystemService(Context.DISPLAY_SERVICE);
- final Display proxyDisplay = displayManager.getDisplay(displayId);
- if (proxyDisplay != null) {
- mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
- }
- }
- }
- return removed;
+ return clearConnectionAndUpdateState(displayId);
}
/**
- * Checks if a display id is being proxy-ed.
+ * Clears all proxy connections belonging to {@code deviceId}.
*/
- public boolean isProxyed(int displayId) {
+ public void clearConnections(int deviceId) {
+ final IntArray displaysToClear = new IntArray();
+ synchronized (mLock) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null && proxy.getDeviceId() == deviceId) {
+ displaysToClear.add(proxy.getDisplayId());
+ }
+ }
+ }
+ for (int i = 0; i < displaysToClear.size(); i++) {
+ clearConnectionAndUpdateState(displaysToClear.get(i));
+ }
+ }
+
+ /**
+ * Removes the system connection of an AccessibilityDisplayProxy.
+ *
+ * This will:
+ * <ul>
+ * <li> Reset Clients to belong to the default device if appropriate.
+ * <li> Stop identifying the display's a11y windows as belonging to a proxy.
+ * <li> Re-enable any input filters for the display.
+ * <li> Notify AMS that a proxy has been removed.
+ * </ul>
+ *
+ * @param displayId the display id of the connection to be cleared.
+ * @return whether the proxy was removed.
+ */
+ private boolean clearConnectionAndUpdateState(int displayId) {
+ boolean removedFromConnections = false;
+ int deviceId = DEVICE_ID_INVALID;
+ synchronized (mLock) {
+ if (mProxyA11yServiceConnections.contains(displayId)) {
+ deviceId = mProxyA11yServiceConnections.get(displayId).getDeviceId();
+ mProxyA11yServiceConnections.remove(displayId);
+ removedFromConnections = true;
+ }
+ }
+
+ if (removedFromConnections) {
+ updateStateForRemovedDisplay(displayId, deviceId);
+ }
+
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Unregistered proxy for display id " + displayId + ": "
+ + removedFromConnections);
+ }
+ return removedFromConnections;
+ }
+
+ /**
+ * When the connection is removed from tracking in ProxyManager, propagate changes to other a11y
+ * system components like the input filter and IAccessibilityManagerClients.
+ */
+ public void updateStateForRemovedDisplay(int displayId, int deviceId) {
+ mA11yWindowManager.stopTrackingDisplayProxy(displayId);
+ // A11yInputFilter isn't thread-safe, so post on the system thread.
+ mMainHandler.post(
+ () -> {
+ if (mA11yInputFilter != null) {
+ final DisplayManager displayManager = (DisplayManager)
+ mContext.getSystemService(Context.DISPLAY_SERVICE);
+ final Display proxyDisplay = displayManager.getDisplay(displayId);
+ if (proxyDisplay != null) {
+ // A11yInputFilter isn't thread-safe, so post on the system thread.
+ mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
+ }
+ }
+ });
+ // If there isn't an existing proxy for the device id, reset clients. Resetting
+ // will usually happen, since in most cases there will only be one proxy for a
+ // device.
+ if (!isProxyedDeviceId(deviceId)) {
+ synchronized (mLock) {
+ mSystemSupport.removeDeviceIdLocked(deviceId);
+ mLastStates.delete(deviceId);
+ }
+ } else {
+ // Update with the states of the remaining proxies.
+ onProxyChanged(deviceId);
+ }
+ }
+
+ /**
+ * Returns {@code true} if {@code displayId} is being proxy-ed.
+ */
+ public boolean isProxyedDisplay(int displayId) {
synchronized (mLock) {
final boolean tracked = mProxyA11yServiceConnections.contains(displayId);
if (DEBUG) {
@@ -176,6 +306,23 @@
}
/**
+ * Returns {@code true} if {@code deviceId} is being proxy-ed.
+ */
+ public boolean isProxyedDeviceId(int deviceId) {
+ if (deviceId == DEVICE_ID_DEFAULT && deviceId == DEVICE_ID_INVALID) {
+ return false;
+ }
+ boolean isTrackingDeviceId;
+ synchronized (mLock) {
+ isTrackingDeviceId = getFirstProxyForDeviceIdLocked(deviceId) != null;
+ }
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Tracking device " + deviceId + " : " + isTrackingDeviceId);
+ }
+ return isTrackingDeviceId;
+ }
+
+ /**
* Sends AccessibilityEvents to a proxy given the event's displayId.
*/
public void sendAccessibilityEventLocked(AccessibilityEvent event) {
@@ -213,15 +360,37 @@
/**
* If there is at least one proxy, accessibility is enabled.
*/
- public int getStateLocked() {
+ public int getStateLocked(int deviceId, boolean automationRunning) {
int clientState = 0;
- final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
- if (a11yEnabled) {
+ if (automationRunning) {
clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
}
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null && proxy.getDeviceId() == deviceId) {
+ // Combine proxy states.
+ clientState |= getStateForDisplayIdLocked(proxy);
+ }
+ }
+
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "For device id " + deviceId + " a11y is enabled: "
+ + ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0));
+ Slog.v(LOG_TAG, "For device id " + deviceId + " touch exploration is enabled: "
+ + ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED)
+ != 0));
+ }
+ return clientState;
+ }
+
+ /**
+ * If there is at least one proxy, accessibility is enabled.
+ */
+ public int getStateForDisplayIdLocked(ProxyAccessibilityServiceConnection proxy) {
+ int clientState = 0;
+ if (proxy != null) {
+ clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
if (proxy.mRequestTouchExplorationMode) {
clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
}
@@ -235,61 +404,396 @@
!= 0));
}
return clientState;
- // TODO(b/254545943): When A11yManager is separated, include support for other properties.
}
/**
- * Gets the last state.
+ * Gets the last state for a device.
*/
- public int getLastSentStateLocked() {
- return mLastState;
+ public int getLastSentStateLocked(int deviceId) {
+ return mLastStates.get(deviceId, 0);
}
/**
- * Sets the last state.
+ * Sets the last state for a device.
*/
- public void setLastStateLocked(int proxyState) {
- mLastState = proxyState;
+ public void setLastStateLocked(int deviceId, int proxyState) {
+ mLastStates.put(deviceId, proxyState);
}
/**
- * Returns the relevant event types of every proxy.
- * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+ * Updates the relevant event types of the app clients that are shown on a display owned by the
+ * specified device.
+ *
+ * A client belongs to a device id, so event types (and other state) is determined by the device
+ * id. In most cases, a device owns a single display. But if multiple displays may belong to one
+ * Virtual Device, the app clients will get the aggregated event types for all proxy-ed displays
+ * belonging to a VirtualDevice.
*/
- public int getRelevantEventTypesLocked() {
+ public void updateRelevantEventTypesLocked(int deviceId) {
+ if (!isProxyedDeviceId(deviceId)) {
+ return;
+ }
+ mMainHandler.post(() -> {
+ synchronized (mLock) {
+ broadcastToClientsLocked(ignoreRemoteException(client -> {
+ int relevantEventTypes;
+ if (client.mDeviceId == deviceId) {
+ relevantEventTypes = computeRelevantEventTypesLocked(client);
+ if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+ client.mLastSentRelevantEventTypes = relevantEventTypes;
+ client.mCallback.setRelevantEventTypes(relevantEventTypes);
+ }
+ }
+ }));
+ }
+ });
+ }
+
+ /**
+ * Returns the relevant event types for a Client.
+ */
+ int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
int relevantEventTypes = 0;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
- ProxyAccessibilityServiceConnection proxy =
+ final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
- relevantEventTypes |= proxy.getRelevantEventTypes();
+ if (proxy != null && proxy.getDeviceId() == client.mDeviceId) {
+ relevantEventTypes |= proxy.getRelevantEventTypes();
+ relevantEventTypes |= AccessibilityManagerService.isClientInPackageAllowlist(
+ mUiAutomationManager.getServiceInfo(), client)
+ ? mUiAutomationManager.getRelevantEventTypes()
+ : 0;
+ }
}
if (DEBUG) {
- Slog.v(LOG_TAG, "Relevant event types for all proxies: "
- + AccessibilityEvent.eventTypeToString(relevantEventTypes));
+ Slog.v(LOG_TAG, "Relevant event types for device id " + client.mDeviceId
+ + ": " + AccessibilityEvent.eventTypeToString(relevantEventTypes));
}
return relevantEventTypes;
}
/**
- * Gets the number of current proxy connections.
- * @return
- */
- public int getNumProxysLocked() {
- return mProxyA11yServiceConnections.size();
- }
-
- /**
* Adds the service interfaces to a list.
- * @param interfaces
+ * @param interfaces the list to add to.
+ * @param deviceId the device id of the interested app client.
*/
- public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
+ public void addServiceInterfacesLocked(@NonNull List<IAccessibilityServiceClient> interfaces,
+ int deviceId) {
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
- final IBinder proxyBinder = proxy.mService;
- final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
- if ((proxyBinder != null) && (proxyInterface != null)) {
- interfaces.add(proxyInterface);
+ if (proxy != null && proxy.getDeviceId() == deviceId) {
+ final IBinder proxyBinder = proxy.mService;
+ final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+ if ((proxyBinder != null) && (proxyInterface != null)) {
+ interfaces.add(proxyInterface);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the list of installed and enabled services for a device id.
+ *
+ * Note: Multiple display proxies may belong to the same device.
+ */
+ public List<AccessibilityServiceInfo> getInstalledAndEnabledServiceInfosLocked(int feedbackType,
+ int deviceId) {
+ List<AccessibilityServiceInfo> serviceInfos = new ArrayList<>();
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null && proxy.getDeviceId() == deviceId) {
+ // Return all proxy infos for ALL mask.
+ if (feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) {
+ serviceInfos.addAll(proxy.getInstalledAndEnabledServices());
+ } else if ((proxy.mFeedbackType & feedbackType) != 0) {
+ List<AccessibilityServiceInfo> proxyInfos =
+ proxy.getInstalledAndEnabledServices();
+ // Iterate through each info in the proxy.
+ for (AccessibilityServiceInfo info : proxyInfos) {
+ if ((info.feedbackType & feedbackType) != 0) {
+ serviceInfos.add(info);
+ }
+ }
+ }
+ }
+ }
+ return serviceInfos;
+ }
+
+ /**
+ * Handles proxy changes.
+ *
+ * <p>
+ * Changes include if the proxy is unregistered, its service info list has
+ * changed, or its focus appearance has changed.
+ * <p>
+ * Some responses may include updating app clients. A client belongs to a device id, so state is
+ * determined by the device id. In most cases, a device owns a single display. But if multiple
+ * displays belong to one Virtual Device, the app clients will get a difference in
+ * behavior depending on what is being updated.
+ *
+ * The following state methods are updated for AccessibilityManager clients belonging to a
+ * proxied device:
+ * <ul>
+ * <li> A11yManager#setRelevantEventTypes - The combined event types of all proxies belonging to
+ * a device id.
+ * <li> A11yManager#setState - The combined states of all proxies belonging to a device id.
+ * <li> A11yManager#notifyServicesStateChanged(timeout) - The highest of all proxies belonging
+ * to a device id.
+ * <li> A11yManager#setFocusAppearance - The appearance of the most recently updated display id
+ * belonging to the device.
+ * </ul>
+ * This is similar to onUserStateChangeLocked and onClientChangeLocked, but does not require an
+ * A11yUserState and only checks proxy-relevant settings.
+ */
+ public void onProxyChanged(int deviceId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "onProxyChanged called for deviceId: " + deviceId);
+ }
+ //The following state updates are excluded:
+ // - Input-related state
+ // - Primary-device / hardware-specific state
+ synchronized (mLock) {
+ // A proxy may be registered after the client has been initialized in #addClient.
+ // For example, a user does not turn on accessibility until after the app has launched.
+ // Or the process was started with a default id context and should shift to a device.
+ // Update device ids of the clients if necessary.
+ updateDeviceIdsIfNeededLocked(deviceId);
+ // Start tracking of all displays if necessary.
+ mSystemSupport.updateWindowsForAccessibilityCallbackLocked();
+ // Calls A11yManager#setRelevantEventTypes (test these)
+ updateRelevantEventTypesLocked(deviceId);
+ // Calls A11yManager#setState
+ scheduleUpdateProxyClientsIfNeededLocked(deviceId);
+ //Calls A11yManager#notifyServicesStateChanged(timeout)
+ scheduleNotifyProxyClientsOfServicesStateChangeLocked(deviceId);
+ // Calls A11yManager#setFocusAppearance
+ updateFocusAppearanceLocked(deviceId);
+ mSystemSupport.notifyClearAccessibilityCacheLocked();
+ }
+ }
+
+ /**
+ * Updates the states of the app AccessibilityManagers.
+ */
+ public void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
+ final int proxyState = getStateLocked(deviceId,
+ mUiAutomationManager.isUiAutomationRunningLocked());
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "State for device id " + deviceId + " is " + proxyState);
+ Slog.v(LOG_TAG, "Last state for device id " + deviceId + " is "
+ + getLastSentStateLocked(deviceId));
+ }
+ if ((getLastSentStateLocked(deviceId)) != proxyState) {
+ setLastStateLocked(deviceId, proxyState);
+ mMainHandler.post(() -> {
+ synchronized (mLock) {
+ broadcastToClientsLocked(ignoreRemoteException(client -> {
+ if (client.mDeviceId == deviceId) {
+ client.mCallback.setState(proxyState);
+ }
+ }));
+ }
+ });
+ }
+ }
+
+ /**
+ * Notifies AccessibilityManager of services state changes, which includes changes to the
+ * list of service infos and timeouts.
+ *
+ * @see AccessibilityManager.AccessibilityServicesStateChangeListener
+ */
+ public void scheduleNotifyProxyClientsOfServicesStateChangeLocked(int deviceId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Notify services state change at device id " + deviceId);
+ }
+ mMainHandler.post(()-> {
+ broadcastToClientsLocked(ignoreRemoteException(client -> {
+ if (client.mDeviceId == deviceId) {
+ synchronized (mLock) {
+ client.mCallback.notifyServicesStateChanged(
+ getRecommendedTimeoutMillisLocked(deviceId));
+ }
+ }
+ }));
+ });
+ }
+
+ /**
+ * Updates the focus appearance of AccessibilityManagerClients.
+ */
+ public void updateFocusAppearanceLocked(int deviceId) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Update proxy focus appearance at device id " + deviceId);
+ }
+ // Reasonably assume that all proxies belonging to a virtual device should have the
+ // same focus appearance, and if they should be different these should belong to different
+ // virtual devices.
+ final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+ if (proxy != null) {
+ mMainHandler.post(()-> {
+ broadcastToClientsLocked(ignoreRemoteException(client -> {
+ if (client.mDeviceId == proxy.getDeviceId()) {
+ client.mCallback.setFocusAppearance(
+ proxy.getFocusStrokeWidthLocked(),
+ proxy.getFocusColorLocked());
+ }
+ }));
+ });
+ }
+ }
+
+ private ProxyAccessibilityServiceConnection getFirstProxyForDeviceIdLocked(int deviceId) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null && proxy.getDeviceId() == deviceId) {
+ return proxy;
+ }
+ }
+ return null;
+ }
+
+ private void broadcastToClientsLocked(
+ @NonNull Consumer<AccessibilityManagerService.Client> clientAction) {
+ final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+ mSystemSupport.getCurrentUserClientsLocked();
+ final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+ mSystemSupport.getGlobalClientsLocked();
+ userClients.broadcastForEachCookie(clientAction);
+ globalClients.broadcastForEachCookie(clientAction);
+ }
+
+ /**
+ * Updates the timeout and notifies app clients.
+ *
+ * For real users, timeouts are tracked in A11yUserState. For proxies, timeouts are in the
+ * service connection. The value in user state is preferred, but if this value is 0 the service
+ * info value is used.
+ *
+ * This follows the pattern in readUserRecommendedUiTimeoutSettingsLocked.
+ *
+ * TODO(b/250929565): ProxyUserState or similar should hold the timeouts
+ */
+ public void updateTimeoutsIfNeeded(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+ synchronized (mLock) {
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null) {
+ if (proxy.updateTimeouts(nonInteractiveUiTimeout, interactiveUiTimeout)) {
+ scheduleNotifyProxyClientsOfServicesStateChangeLocked(proxy.getDeviceId());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the recommended timeout belonging to a Virtual Device.
+ *
+ * This is the highest of all display proxies belonging to the virtual device.
+ */
+ public long getRecommendedTimeoutMillisLocked(int deviceId) {
+ int combinedInteractiveTimeout = 0;
+ int combinedNonInteractiveTimeout = 0;
+ for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+ final ProxyAccessibilityServiceConnection proxy =
+ mProxyA11yServiceConnections.valueAt(i);
+ if (proxy != null && proxy.getDeviceId() == deviceId) {
+ final int proxyInteractiveUiTimeout =
+ (proxy != null) ? proxy.getInteractiveTimeout() : 0;
+ final int nonInteractiveUiTimeout =
+ (proxy != null) ? proxy.getNonInteractiveTimeout() : 0;
+ combinedInteractiveTimeout = Math.max(proxyInteractiveUiTimeout,
+ combinedInteractiveTimeout);
+ combinedNonInteractiveTimeout = Math.max(nonInteractiveUiTimeout,
+ combinedNonInteractiveTimeout);
+ }
+ }
+ return IntPair.of(combinedInteractiveTimeout, combinedNonInteractiveTimeout);
+ }
+
+ /**
+ * Gets the first focus stroke width belonging to the device.
+ */
+ public int getFocusStrokeWidthLocked(int deviceId) {
+ final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+ if (proxy != null) {
+ return proxy.getFocusStrokeWidthLocked();
+ }
+ return 0;
+
+ }
+
+ /**
+ * Gets the first focus color belonging to the device.
+ */
+ public int getFocusColorLocked(int deviceId) {
+ final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+ if (proxy != null) {
+ return proxy.getFocusColorLocked();
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the first device id given a UID.
+ * @param callingUid the UID to check.
+ * @return the first matching device id, or DEVICE_ID_INVALID.
+ */
+ public int getFirstDeviceIdForUidLocked(int callingUid) {
+ int firstDeviceId = DEVICE_ID_INVALID;
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm == null) {
+ return firstDeviceId;
+ }
+ final Set<Integer> deviceIds = localVdm.getDeviceIdsForUid(callingUid);
+ for (Integer uidDeviceId : deviceIds) {
+ if (uidDeviceId != DEVICE_ID_DEFAULT && uidDeviceId != DEVICE_ID_INVALID) {
+ firstDeviceId = uidDeviceId;
+ break;
+ }
+ }
+ return firstDeviceId;
+ }
+
+ /**
+ * Sets a Client device id if the app uid belongs to the virtual device.
+ */
+ public void updateDeviceIdsIfNeededLocked(int deviceId) {
+ final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+ mSystemSupport.getCurrentUserClientsLocked();
+ final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+ mSystemSupport.getGlobalClientsLocked();
+
+ updateDeviceIdsIfNeededLocked(deviceId, userClients);
+ updateDeviceIdsIfNeededLocked(deviceId, globalClients);
+ }
+
+ /**
+ * Updates the device ids of IAccessibilityManagerClients if needed.
+ */
+ public void updateDeviceIdsIfNeededLocked(int deviceId,
+ @NonNull RemoteCallbackList<IAccessibilityManagerClient> clients) {
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (localVdm == null) {
+ return;
+ }
+
+ for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+ final AccessibilityManagerService.Client client =
+ ((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
+ if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
+ && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+ + Arrays.toString(client.mPackageNames));
+ }
+ client.mDeviceId = deviceId;
}
}
}
@@ -306,9 +810,18 @@
}
void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "Set proxy input filter to " + filter);
+ }
mA11yInputFilter = filter;
}
+ VirtualDeviceManagerInternal getLocalVdm() {
+ if (mLocalVdm == null) {
+ mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
+ }
+ return mLocalVdm;
+ }
/**
* Prints information belonging to each display that is controlled by an
@@ -320,13 +833,38 @@
pw.println("Proxy manager state:");
pw.println(" Number of proxy connections: " + mProxyA11yServiceConnections.size());
pw.println(" Registered proxy connections:");
+ final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+ mSystemSupport.getCurrentUserClientsLocked();
+ final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+ mSystemSupport.getGlobalClientsLocked();
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null) {
proxy.dump(fd, pw, args);
}
+ pw.println();
+ pw.println(" User clients for proxy's virtual device id");
+ printClientsForDeviceId(pw, userClients, proxy.getDeviceId());
+ pw.println();
+ pw.println(" Global clients for proxy's virtual device id");
+ printClientsForDeviceId(pw, globalClients, proxy.getDeviceId());
+
}
}
}
-}
\ No newline at end of file
+
+ private void printClientsForDeviceId(PrintWriter pw,
+ RemoteCallbackList<IAccessibilityManagerClient> clients, int deviceId) {
+ if (clients != null) {
+ for (int j = 0; j < clients.getRegisteredCallbackCount(); j++) {
+ final AccessibilityManagerService.Client client =
+ (AccessibilityManagerService.Client)
+ clients.getRegisteredCallbackCookie(j);
+ if (client.mDeviceId == deviceId) {
+ pw.println(" " + Arrays.toString(client.mPackageNames) + "\n");
+ }
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index f3a949d..dd7d38f 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -133,18 +133,6 @@
@UserIdInt int userId, int associationId) {
final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
- // Check if the request's data type has been requested before.
- List<SystemDataTransferRequest> storedRequests =
- mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
- associationId);
- for (SystemDataTransferRequest storedRequest : storedRequests) {
- if (storedRequest instanceof PermissionSyncRequest) {
- Slog.e(LOG_TAG, "The request has been sent before, you can not send "
- + "the same request type again.");
- return null;
- }
- }
-
Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ "] associationId [" + associationId + "]");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b338d89..1363ef3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1046,18 +1046,30 @@
*/
void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
Looper looper) {
+ ArrayList<Integer> displayIdsForUid = getDisplayIdsWhereUidIsRunning(uid);
+ if (displayIdsForUid.isEmpty()) {
+ return;
+ }
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ for (int i = 0; i < displayIdsForUid.size(); i++) {
+ Display display = displayManager.getDisplay(displayIdsForUid.get(i));
+ if (display != null && display.isValid()) {
+ Toast.makeText(mContext.createDisplayContext(display), looper, text,
+ duration).show();
+ }
+ }
+ }
+
+ private ArrayList<Integer> getDisplayIdsWhereUidIsRunning(int uid) {
+ ArrayList<Integer> displayIdsForUid = new ArrayList<>();
synchronized (mVirtualDeviceLock) {
- DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
if (mVirtualDisplays.valueAt(i).getWindowPolicyController().containsUid(uid)) {
- Display display = displayManager.getDisplay(mVirtualDisplays.keyAt(i));
- if (display != null && display.isValid()) {
- Toast.makeText(mContext.createDisplayContext(display), looper, text,
- duration).show();
- }
+ displayIdsForUid.add(mVirtualDisplays.keyAt(i));
}
}
}
+ return displayIdsForUid;
}
boolean isDisplayOwnedByVirtualDevice(int displayId) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8fe61e7..78cbf2b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -807,9 +807,9 @@
ServiceRecord r = res.record;
// Note, when startService() or startForegroundService() is called on an already
// running SHORT_SERVICE FGS, the call will succeed (i.e. we won't throw
- // ForegroundServiceStartNotAllowedException), even when the service is alerady timed
- // out. This is because these APIs will essnetially only change the "started" state
- // of the service, and it won't afect "the foreground-ness" of the service, or the type
+ // ForegroundServiceStartNotAllowedException), even when the service is already timed
+ // out. This is because these APIs will essentially only change the "started" state
+ // of the service, and it won't affect "the foreground-ness" of the service, or the type
// of the FGS.
// However, this call will still _not_ extend the SHORT_SERVICE timeout either.
// Also, if the app tries to change the type of the FGS later (using
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 82c4796a..553706d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -152,7 +152,7 @@
static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
- private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
+ 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;
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
@@ -876,7 +876,7 @@
private static final String KEY_MAX_EMPTY_TIME_MILLIS =
"max_empty_time_millis";
- private static final long DEFAULT_MAX_EMPTY_TIME_MILLIS = 30 * 60 * 1000;
+ private static final long DEFAULT_MAX_EMPTY_TIME_MILLIS = 1000L * 60L * 60L * 1000L;
volatile long mMaxEmptyTimeMillis = DEFAULT_MAX_EMPTY_TIME_MILLIS;
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 9079ba8..5dd0a3f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -16,6 +16,11 @@
package com.android.server.am;
+import android.util.Log;
+import android.util.LogWriter;
+
+import java.io.PrintWriter;
+
/**
* Common class for the various debug {@link android.util.Log} output configuration in the activity
* manager package.
@@ -38,6 +43,10 @@
// Default log tag for the activity manager package.
static final String TAG_AM = "ActivityManager";
+ // Default writer that emits "info" log events for the activity manager package.
+ static final PrintWriter LOG_WRITER_INFO = new PrintWriter(
+ new LogWriter(Log.INFO, TAG_AM));
+
// Enable all debug log categories.
static final boolean DEBUG_ALL = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 74676cf..06e6df9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
@@ -13650,8 +13651,9 @@
|| action.startsWith("android.intent.action.UID_")
|| action.startsWith("android.intent.action.EXTERNAL_")) {
if (DEBUG_BROADCAST) {
- Slog.wtf(TAG, "System internals registering for " + filter
- + " with app priority; this will race with apps!",
+ Slog.wtf(TAG,
+ "System internals registering for " + filter.toLongString()
+ + " with app priority; this will race with apps!",
new Throwable());
}
@@ -13746,17 +13748,6 @@
+ "RECEIVER_NOT_EXPORTED flag");
}
- // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
- // updating their receivers to be exempt from this requirement until their receivers
- // are flagged.
- if (requireExplicitFlagForDynamicReceivers) {
- if ("com.shannon.imsservice".equals(callerPackage)) {
- // Note, a versionCode check for this package is not performed because this
- // package consumes the SecurityException, so it wouldn't be caught during
- // presubmit.
- requireExplicitFlagForDynamicReceivers = false;
- }
- }
if (!onlyProtectedBroadcasts) {
if (receiver == null && !explicitExportStateDefined) {
// sticky broadcast, no flag specified (flag isn't required)
@@ -18725,27 +18716,25 @@
@Override
public void waitForBroadcastIdle() {
- waitForBroadcastIdle(/* printWriter= */ null);
+ waitForBroadcastIdle(LOG_WRITER_INFO);
}
- public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
+ public void waitForBroadcastIdle(@NonNull PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
BroadcastLoopers.waitForIdle(pw);
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForIdle(pw);
}
- if (pw != null) {
- pw.println("All broadcast queues are idle!");
- pw.flush();
- }
+ pw.println("All broadcast queues are idle!");
+ pw.flush();
}
@Override
public void waitForBroadcastBarrier() {
- waitForBroadcastBarrier(/* printWriter= */ null, false, false);
+ waitForBroadcastBarrier(LOG_WRITER_INFO, false, false);
}
- public void waitForBroadcastBarrier(@Nullable PrintWriter pw,
+ public void waitForBroadcastBarrier(@NonNull PrintWriter pw,
boolean flushBroadcastLoopers, boolean flushApplicationThreads) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
if (flushBroadcastLoopers) {
@@ -18763,11 +18752,7 @@
* Wait for all pending {@link IApplicationThread} events to be processed in
* all currently running apps.
*/
- public void waitForApplicationBarrier(@Nullable PrintWriter pw) {
- if (pw == null) {
- pw = new PrintWriter(new LogWriter(Log.VERBOSE, TAG));
- }
-
+ public void waitForApplicationBarrier(@NonNull PrintWriter pw) {
final CountDownLatch finishedLatch = new CountDownLatch(1);
final AtomicInteger pingCount = new AtomicInteger(0);
final AtomicInteger pongCount = new AtomicInteger(0);
@@ -18815,15 +18800,18 @@
try {
if (finishedLatch.await(1, TimeUnit.SECONDS)) {
pw.println("Finished application barriers!");
+ pw.flush();
return;
} else {
pw.println("Waiting for application barriers, at " + pongCount.get() + " of "
+ pingCount.get() + "...");
+ pw.flush();
}
} catch (InterruptedException ignored) {
}
}
pw.println("Gave up waiting for application barriers!");
+ pw.flush();
}
void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 72e17d8..350ac3b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -38,6 +38,7 @@
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
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.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
@@ -112,6 +113,7 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.DisplayMetrics;
+import android.util.TeeWriter;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.window.SplashScreen;
@@ -1093,7 +1095,7 @@
synchronized (mInternal.mProcLock) {
mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app,
CachedAppOptimizer.CompactProfile.FULL,
- CachedAppOptimizer.CompactSource.APP, true);
+ CachedAppOptimizer.CompactSource.SHELL, true);
}
pw.println("Finished full compaction for " + app.mPid);
} else if (isSomeCompact) {
@@ -1101,7 +1103,7 @@
synchronized (mInternal.mProcLock) {
mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app,
CachedAppOptimizer.CompactProfile.SOME,
- CachedAppOptimizer.CompactSource.APP, true);
+ CachedAppOptimizer.CompactSource.SHELL, true);
}
pw.println("Finished some compaction for " + app.mPid);
}
@@ -3364,11 +3366,13 @@
}
int runWaitForBroadcastIdle(PrintWriter pw) throws RemoteException {
+ pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
mInternal.waitForBroadcastIdle(pw);
return 0;
}
int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
+ pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
boolean flushBroadcastLoopers = false;
boolean flushApplicationThreads = false;
String opt;
@@ -3387,6 +3391,7 @@
}
int runWaitForApplicationBarrier(PrintWriter pw) throws RemoteException {
+ pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
mInternal.waitForApplicationBarrier(pw);
return 0;
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d9ba845..ed297d0 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -654,6 +654,7 @@
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
mHandler.post(() -> {
+ mCpuWakeupStats.onUidRemoved(uid);
synchronized (mStats) {
mStats.removeUidStatsLocked(uid, elapsedRealtime);
}
@@ -764,6 +765,7 @@
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
+ mCpuWakeupStats.noteUidProcessState(uid, state);
synchronized (mStats) {
mStats.noteUidProcessStateLocked(uid, state, elapsedRealtime, uptime);
}
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index a5535cb..92547ea 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -74,7 +73,7 @@
* defined by {@link MessageQueue#isIdle()}. Note that {@link Message#when}
* still in the future are ignored for the purposes of the idle test.
*/
- public static void waitForIdle(@Nullable PrintWriter pw) {
+ public static void waitForIdle(@NonNull PrintWriter pw) {
waitForCondition(pw, (looper, latch) -> {
final MessageQueue queue = looper.getQueue();
queue.addIdleHandler(() -> {
@@ -89,7 +88,7 @@
* Note that {@link Message#when} still in the future are ignored for the purposes
* of the idle test.
*/
- public static void waitForBarrier(@Nullable PrintWriter pw) {
+ public static void waitForBarrier(@NonNull PrintWriter pw) {
waitForCondition(pw, (looper, latch) -> {
(new Handler(looper)).post(() -> {
latch.countDown();
@@ -100,7 +99,7 @@
/**
* Wait for all registered {@link Looper} instances to meet a certain condition.
*/
- private static void waitForCondition(@Nullable PrintWriter pw,
+ private static void waitForCondition(@NonNull PrintWriter pw,
@NonNull BiConsumer<Looper, CountDownLatch> condition) {
final CountDownLatch latch;
synchronized (sLoopers) {
@@ -122,18 +121,12 @@
final long now = SystemClock.uptimeMillis();
if (now >= lastPrint + 1000) {
lastPrint = now;
- logv("Waiting for " + latch.getCount() + " loopers to drain...", pw);
+ pw.println("Waiting for " + latch.getCount() + " loopers to drain...");
+ pw.flush();
}
SystemClock.sleep(100);
}
- logv("Loopers drained!", pw);
- }
-
- private static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
+ pw.println("Loopers drained!");
+ pw.flush();
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 056e17a..48ab96c 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -864,6 +864,7 @@
static final int REASON_CONTAINS_RESULT_TO = 15;
static final int REASON_CONTAINS_INSTRUMENTED = 16;
static final int REASON_CONTAINS_MANIFEST = 17;
+ static final int REASON_FOREGROUND_ACTIVITIES = 18;
@IntDef(flag = false, prefix = { "REASON_" }, value = {
REASON_EMPTY,
@@ -883,6 +884,7 @@
REASON_CONTAINS_RESULT_TO,
REASON_CONTAINS_INSTRUMENTED,
REASON_CONTAINS_MANIFEST,
+ REASON_FOREGROUND_ACTIVITIES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -906,6 +908,7 @@
case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO";
case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED";
case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST";
+ case REASON_FOREGROUND_ACTIVITIES: return "FOREGROUND_ACTIVITIES";
default: return Integer.toString(reason);
}
}
@@ -963,6 +966,11 @@
} else if (mProcessInstrumented) {
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_INSTRUMENTED;
+ } else if (app != null && app.hasForegroundActivities()) {
+ // TODO: Listen for uid state changes to check when an uid goes in and out of
+ // the TOP state.
+ mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+ mRunnableAtReason = REASON_FOREGROUND_ACTIVITIES;
} else if (mCountOrdered > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 75e9336..6d1344d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -69,14 +69,6 @@
Slog.v(TAG, msg);
}
- static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
- logv(msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- }
-
static void checkState(boolean expression, @NonNull String msg) {
if (!expression) {
throw new IllegalStateException(msg);
@@ -219,7 +211,7 @@
* since running apps can continue sending new broadcasts in perpetuity;
* consider using {@link #waitForBarrier} instead.
*/
- public abstract void waitForIdle(@Nullable PrintWriter pw);
+ public abstract void waitForIdle(@NonNull PrintWriter pw);
/**
* Wait until any currently waiting broadcasts have been dispatched.
@@ -230,7 +222,7 @@
* Callers are advised that this method will <em>not</em> wait for any
* future broadcasts that are newly enqueued after being invoked.
*/
- public abstract void waitForBarrier(@Nullable PrintWriter pw);
+ public abstract void waitForBarrier(@NonNull PrintWriter pw);
/**
* Delays delivering broadcasts to the specified package.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 4b8dc99..bd36c3f 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -835,7 +835,7 @@
OOM_ADJ_REASON_START_RECEIVER);
}
} else if (filter.receiverList.app != null) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
+ mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
}
@@ -1129,7 +1129,7 @@
}
if (sendResult) {
if (r.callerApp != null) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+ mService.mOomAdjuster.unfreezeTemporarily(
r.callerApp,
CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 7157fff..a1fccbc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -28,6 +28,7 @@
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
@@ -927,7 +928,7 @@
final ProcessRecord app = r.resultToApp;
final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
if (thread != null) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
+ mService.mOomAdjuster.unfreezeTemporarily(
app, CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
if (r.shareIdentity && app.uid != r.callingUid) {
mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
@@ -1247,7 +1248,7 @@
* the given {@link Predicate}.
*/
private boolean testAllProcessQueues(@NonNull Predicate<BroadcastProcessQueue> test,
- @NonNull String label, @Nullable PrintWriter pw) {
+ @NonNull String label, @NonNull PrintWriter pw) {
for (int i = 0; i < mProcessQueues.size(); i++) {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
@@ -1255,14 +1256,16 @@
final long now = SystemClock.uptimeMillis();
if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
mLastTestFailureTime = now;
- logv("Test " + label + " failed due to " + leaf.toShortString(), pw);
+ pw.println("Test " + label + " failed due to " + leaf.toShortString());
+ pw.flush();
}
return false;
}
leaf = leaf.processNameNext;
}
}
- logv("Test " + label + " passed", pw);
+ pw.println("Test " + label + " passed");
+ pw.flush();
return true;
}
@@ -1349,30 +1352,30 @@
@Override
public boolean isIdleLocked() {
- return isIdleLocked(null);
+ return isIdleLocked(LOG_WRITER_INFO);
}
- public boolean isIdleLocked(@Nullable PrintWriter pw) {
+ public boolean isIdleLocked(@NonNull PrintWriter pw) {
return testAllProcessQueues(q -> q.isIdle(), "idle", pw);
}
@Override
public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
- return isBeyondBarrierLocked(barrierTime, null);
+ return isBeyondBarrierLocked(barrierTime, LOG_WRITER_INFO);
}
public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime,
- @Nullable PrintWriter pw) {
+ @NonNull PrintWriter pw) {
return testAllProcessQueues(q -> q.isBeyondBarrierLocked(barrierTime), "barrier", pw);
}
@Override
- public void waitForIdle(@Nullable PrintWriter pw) {
+ public void waitForIdle(@NonNull PrintWriter pw) {
waitFor(() -> isIdleLocked(pw));
}
@Override
- public void waitForBarrier(@Nullable PrintWriter pw) {
+ public void waitForBarrier(@NonNull PrintWriter pw) {
final long now = SystemClock.uptimeMillis();
waitFor(() -> isBeyondBarrierLocked(now, pw));
}
@@ -1515,7 +1518,7 @@
mService.updateLruProcessLocked(queue.app, false, null);
}
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
+ mService.mOomAdjuster.unfreezeTemporarily(queue.app,
CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
if (queue.runningOomAdjusted) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6c9f602..9c15463 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -198,7 +198,7 @@
// Format of this string should be a comma separated list of integers.
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
- @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L;
+ @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
@VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
@VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
@@ -226,8 +226,8 @@
FULL // File+anon compaction
}
- // This indicates the process OOM memory state that initiated the compaction request
- public enum CompactSource { APP, PERSISTENT, BFGS }
+ // This indicates who initiated the compaction request
+ public enum CompactSource { APP, SHELL }
public enum CancelCompactReason {
SCREEN_ON, // screen was turned on which cancels all compactions.
@@ -373,10 +373,6 @@
@GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
- @GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
- @GuardedBy("mPhenotypeFlagLock")
@VisibleForTesting volatile long mCompactThrottleMinOomAdj =
DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
@GuardedBy("mPhenotypeFlagLock")
@@ -635,8 +631,6 @@
pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
pw.println(" " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
- pw.println(" " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
- pw.println(" " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
pw.println(" " + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ + "=" + mCompactThrottleMinOomAdj);
pw.println(" " + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ + "=" + mCompactThrottleMaxOomAdj);
pw.println(" " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mCompactStatsdSampleRate);
@@ -728,32 +722,6 @@
}
}
- // This method returns true only if requirements are met. Note, that requirements are different
- // from throttles applied at the time a compaction is trying to be executed in the sense that
- // these are not subject to change dependent on time or memory as throttles usually do.
- @GuardedBy("mProcLock")
- boolean meetsCompactionRequirements(ProcessRecord proc) {
- if (mAm.mInternal.isPendingTopUid(proc.uid)) {
- // In case the OOM Adjust has not yet been propagated we see if this is
- // pending on becoming top app in which case we should not compact.
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, "Skip compaction since UID is active for " + proc.processName);
- }
- return false;
- }
-
- if (proc.mState.hasForegroundActivities()) {
- if (DEBUG_COMPACTION) {
- Slog.e(TAG_AM,
- "Skip compaction as process " + proc.processName
- + " has foreground activities");
- }
- return false;
- }
-
- return true;
- }
-
@GuardedBy("mProcLock")
boolean compactApp(
ProcessRecord app, CompactProfile compactProfile, CompactSource source, boolean force) {
@@ -777,7 +745,7 @@
return false;
}
- if (!app.mOptRecord.hasPendingCompact() && (meetsCompactionRequirements(app) || force)) {
+ if (!app.mOptRecord.hasPendingCompact()) {
final String processName = (app.processName != null ? app.processName : "");
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
@@ -795,8 +763,7 @@
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM,
" compactApp Skipped for " + app.processName + " pendingCompact= "
- + app.mOptRecord.hasPendingCompact() + " meetsCompactionRequirements="
- + meetsCompactionRequirements(app) + ". Requested compact profile: "
+ + app.mOptRecord.hasPendingCompact() + ". Requested compact profile: "
+ app.mOptRecord.getReqCompactProfile().name() + ". Compact source "
+ app.mOptRecord.getReqCompactSource().name());
}
@@ -831,18 +798,6 @@
return stats;
}
- @GuardedBy("mProcLock")
- boolean shouldCompactPersistent(ProcessRecord app, long now) {
- return (app.mOptRecord.getLastCompactTime() == 0
- || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottlePersistent);
- }
-
- @GuardedBy("mProcLock")
- boolean shouldCompactBFGS(ProcessRecord app, long now) {
- return (app.mOptRecord.getLastCompactTime() == 0
- || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottleBFGS);
- }
-
void compactAllSystem() {
if (useCompaction()) {
if (DEBUG_COMPACTION) {
@@ -1130,8 +1085,6 @@
mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
- mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
- mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
mCompactThrottleMinOomAdj = Long.parseLong(throttleMinOomAdjFlag);
mCompactThrottleMaxOomAdj = Long.parseLong(throttleMaxOomAdjFlag);
} catch (NumberFormatException e) {
@@ -1144,8 +1097,6 @@
mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
- mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
- mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
}
@@ -1497,23 +1448,23 @@
@GuardedBy({"mService", "mProcLock"})
void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
- // Cancel any currently executing compactions
- // if the process moved out of cached state
- if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
- cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
- }
-
- if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
- && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
- if (ENABLE_FILE_COMPACT) {
- // Perform a minor compaction when a perceptible app becomes the prev/home app
- compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+ if (useCompaction()) {
+ // Cancel any currently executing compactions
+ // if the process moved out of cached state
+ if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+ cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
}
- } else if (oldAdj < ProcessList.CACHED_APP_MIN_ADJ
- && newAdj >= ProcessList.CACHED_APP_MIN_ADJ
- && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
- // Perform a major compaction when any app enters cached
- compactApp(app, CompactProfile.FULL, CompactSource.APP, false);
+ }
+ }
+
+ /**
+ * Callback received after a process has been frozen.
+ */
+ void onProcessFrozen(ProcessRecord frozenProc) {
+ if (useCompaction()) {
+ synchronized (mProcLock) {
+ compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
+ }
}
}
@@ -1687,26 +1638,6 @@
return true;
}
}
- } else if (source == CompactSource.PERSISTENT) {
- if (start - lastCompactTime < mCompactThrottlePersistent) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM,
- "Skipping persistent compaction for " + name
- + ": too soon. throttle=" + mCompactThrottlePersistent
- + " last=" + (start - lastCompactTime) + "ms ago");
- }
- return true;
- }
- } else if (source == CompactSource.BFGS) {
- if (start - lastCompactTime < mCompactThrottleBFGS) {
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM,
- "Skipping bfgs compaction for " + name
- + ": too soon. throttle=" + mCompactThrottleBFGS
- + " last=" + (start - lastCompactTime) + "ms ago");
- }
- return true;
- }
}
}
@@ -2021,6 +1952,9 @@
}
}
}
+ if (proc.mOptRecord.isFrozen()) {
+ onProcessFrozen(proc);
+ }
}
break;
case REPORT_UNFREEZE_MSG:
@@ -2050,6 +1984,10 @@
freezeAppAsyncLSP(proc);
}
+ /**
+ * Freeze a process.
+ * @param proc process to be frozen
+ */
@GuardedBy({"mAm"})
private void freezeProcess(final ProcessRecord proc) {
int pid = proc.getPid(); // Unlocked intentionally
@@ -2083,6 +2021,10 @@
if (pid == 0 || opt.isFrozen()) {
// Already frozen or not a real process, either one being
// launched or one being killed
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "Skipping freeze for process " + pid
+ + " " + name + ". Already frozen or not a real process");
+ }
return;
}
@@ -2124,7 +2066,7 @@
frozen = opt.isFrozen();
final UidRecord uidRec = proc.getUidRecord();
- if (frozen && uidRec.areAllProcessesFrozen()) {
+ if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
uidRec.setFrozen(true);
mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
UID_FROZEN_STATE_CHANGED_MSG, proc));
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 84a8099..a98571b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -349,6 +349,7 @@
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<>();
@@ -2936,30 +2937,8 @@
int changes = 0;
- // don't compact during bootup
- if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
- // Cached and prev/home compaction
- // reminder: here, setAdj is previous state, curAdj is upcoming state
- if (state.getCurAdj() != state.getSetAdj()) {
- mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
- } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
- // See if we can compact persistent and bfgs services now that screen is off
- if (state.getSetAdj() < FOREGROUND_APP_ADJ
- && !state.isRunningRemoteAnimation()
- // Because these can fire independent of oom_adj/procstate changes, we need
- // to throttle the actual dispatch of these requests in addition to the
- // processing of the requests. As a result, there is throttling both here
- // and in CachedAppOptimizer.
- && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
- mCachedAppOptimizer.compactApp(app, CachedAppOptimizer.CompactProfile.FULL,
- CachedAppOptimizer.CompactSource.PERSISTENT, false);
- } else if (state.getCurProcState()
- == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
- mCachedAppOptimizer.compactApp(app, CachedAppOptimizer.CompactProfile.FULL,
- CachedAppOptimizer.CompactSource.BFGS, false);
- }
- }
+ if (state.getCurAdj() != state.getSetAdj()) {
+ mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
}
if (state.getCurAdj() != state.getSetAdj()) {
@@ -3472,4 +3451,29 @@
CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
}
}
+
+ @GuardedBy("mService")
+ void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ if (!mCachedAppOptimizer.useFreezer()) {
+ return;
+ }
+
+ final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+ if (!opt.isFrozen() && !opt.isPendingFreeze()) {
+ return;
+ }
+
+ final ArrayList<ProcessRecord> processes = mTmpProcessList;
+ final ActiveUids uids = mTmpUidRecords;
+ mTmpProcessSet.add(app);
+ collectReachableProcessesLocked(mTmpProcessSet, processes, uids);
+ mTmpProcessSet.clear();
+ // Now processes contains app's downstream and app
+ final int size = processes.size();
+ for (int i = 0; i < size; i++) {
+ ProcessRecord proc = processes.get(i);
+ mCachedAppOptimizer.unfreezeTemporarily(proc, reason);
+ }
+ processes.clear();
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 7788ea4..8fce888 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -22,7 +22,9 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ProcessRecord.TAG;
+import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AnrController;
import android.app.ApplicationErrorReport;
@@ -56,6 +58,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ResourcePressureUtil;
import com.android.server.criticalevents.CriticalEventLog;
+import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
import com.android.server.wm.WindowProcessController;
import java.io.File;
@@ -400,6 +403,8 @@
});
}
}
+ // Build memory headers for the ANRing process.
+ String memoryHeaders = buildMemoryHeadersFor(pid);
// Get critical event log before logging the ANR so that it doesn't occur in the log.
latencyTracker.criticalEventLogStarted();
@@ -500,7 +505,8 @@
File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
- criticalEventLog, auxiliaryTaskExecutor, firstPidFilePromise, latencyTracker);
+ criticalEventLog, memoryHeaders, auxiliaryTaskExecutor, firstPidFilePromise,
+ latencyTracker);
if (isMonitorCpuUsage()) {
// Wait for the first call to finish
@@ -714,6 +720,27 @@
resolver.getUserId()) != 0;
}
+ private @Nullable String buildMemoryHeadersFor(int pid) {
+ if (pid <= 0) {
+ Slog.i(TAG, "Memory header requested with invalid pid: " + pid);
+ return null;
+ }
+ MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+ if (snapshot == null) {
+ Slog.i(TAG, "Failed to get memory snapshot for pid:" + pid);
+ return null;
+ }
+
+ StringBuilder memoryHeaders = new StringBuilder();
+ memoryHeaders.append("RssHwmKb: ")
+ .append(snapshot.rssHighWaterMarkInKilobytes)
+ .append("\n");
+ memoryHeaders.append("RssKb: ").append(snapshot.rssInKilobytes).append("\n");
+ memoryHeaders.append("RssAnonKb: ").append(snapshot.anonRssInKilobytes).append("\n");
+ memoryHeaders.append("RssShmemKb: ").append(snapshot.rssShmemKilobytes).append("\n");
+ memoryHeaders.append("VmSwapKb: ").append(snapshot.swapInKilobytes).append("\n");
+ return memoryHeaders.toString();
+ }
/**
* Unless configured otherwise, swallow ANRs in background processes & kill the process.
* Non-private access is for tests only.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 50d00b4..e651e23 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1050,6 +1050,11 @@
return mState.isCached();
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ public boolean hasForegroundActivities() {
+ return mState.hasForegroundActivities();
+ }
+
boolean hasActivities() {
return mWindowProcessController.hasActivities();
}
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index 2e99e97..d9553a3 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -93,7 +93,7 @@
Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
@NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
- logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, null,
+ logExceptionCreatingFile, null, null, null, null, auxiliaryTaskExecutor, null,
latencyTracker);
}
@@ -108,7 +108,7 @@
AnrLatencyTracker latencyTracker) {
return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
logExceptionCreatingFile, null, subject, criticalEventSection,
- auxiliaryTaskExecutor, null, latencyTracker);
+ /* memoryHeaders= */ null, auxiliaryTaskExecutor, null, latencyTracker);
}
/**
@@ -119,8 +119,8 @@
ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
- @NonNull Executor auxiliaryTaskExecutor, Future<File> firstPidFilePromise,
- AnrLatencyTracker latencyTracker) {
+ String memoryHeaders, @NonNull Executor auxiliaryTaskExecutor,
+ Future<File> firstPidFilePromise, AnrLatencyTracker latencyTracker) {
try {
if (latencyTracker != null) {
@@ -160,9 +160,10 @@
return null;
}
- if (subject != null || criticalEventSection != null) {
+ if (subject != null || criticalEventSection != null || memoryHeaders != null) {
appendtoANRFile(tracesFile.getAbsolutePath(),
- (subject != null ? "Subject: " + subject + "\n\n" : "")
+ (subject != null ? "Subject: " + subject + "\n" : "")
+ + (memoryHeaders != null ? memoryHeaders + "\n\n" : "")
+ (criticalEventSection != null ? criticalEventSection : ""));
}
diff --git a/services/core/java/com/android/server/appop/AppOpMigrationHelper.java b/services/core/java/com/android/server/appop/AppOpMigrationHelper.java
new file mode 100644
index 0000000..bc59dd6
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpMigrationHelper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.annotation.NonNull;
+
+import java.util.Map;
+
+/**
+ * In-process api for app-ops migration.
+ *
+ * @hide
+ */
+public interface AppOpMigrationHelper {
+
+ /**
+ * @return a map of app ID to app-op modes (op name -> mode) for a given user.
+ */
+ @NonNull
+ Map<Integer, Map<String, Integer>> getLegacyAppIdAppOpModes(int userId);
+
+ /**
+ * @return a map of package name to app-op modes (op name -> mode) for a given user.
+ */
+ @NonNull
+ Map<String, Map<String, Integer>> getLegacyPackageAppOpModes(int userId);
+
+ /**
+ * @return AppOps file version, the version is same for all the user.
+ */
+ int getLegacyAppOpVersion();
+}
diff --git a/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java b/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java
new file mode 100644
index 0000000..d81a13f
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpMigrationHelperImpl.java
@@ -0,0 +1,155 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Provider of legacy app-ops data for new permission subsystem.
+ *
+ * @hide
+ */
+public class AppOpMigrationHelperImpl implements AppOpMigrationHelper {
+ private SparseArray<Map<Integer, Map<String, Integer>>> mAppIdAppOpModes = null;
+ private SparseArray<Map<String, Map<String, Integer>>> mPackageAppOpModes = null;
+ private int mVersionAtBoot;
+
+ private final Object mLock = new Object();
+
+ @Override
+ @GuardedBy("mLock")
+ @NonNull
+ public Map<Integer, Map<String, Integer>> getLegacyAppIdAppOpModes(int userId) {
+ synchronized (mLock) {
+ if (mAppIdAppOpModes == null) {
+ readLegacyAppOpState();
+ }
+ }
+ return mAppIdAppOpModes.get(userId, Collections.emptyMap());
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ @NonNull
+ public Map<String, Map<String, Integer>> getLegacyPackageAppOpModes(int userId) {
+ synchronized (mLock) {
+ if (mPackageAppOpModes == null) {
+ readLegacyAppOpState();
+ }
+ }
+ return mPackageAppOpModes.get(userId, Collections.emptyMap());
+ }
+
+ @GuardedBy("mLock")
+ private void readLegacyAppOpState() {
+ final File systemDir = SystemServiceManager.ensureSystemDir();
+ AtomicFile appOpFile = new AtomicFile(new File(systemDir, "appops.xml"));
+
+ final SparseArray<SparseIntArray> uidAppOpModes = new SparseArray<>();
+ final SparseArray<ArrayMap<String, SparseIntArray>> packageAppOpModes =
+ new SparseArray<>();
+
+ LegacyAppOpStateParser parser = new LegacyAppOpStateParser();
+ mVersionAtBoot = parser.readState(appOpFile, uidAppOpModes, packageAppOpModes);
+ mAppIdAppOpModes = getAppIdAppOpModes(uidAppOpModes);
+ mPackageAppOpModes = getPackageAppOpModes(packageAppOpModes);
+ }
+
+ private SparseArray<Map<Integer, Map<String, Integer>>> getAppIdAppOpModes(
+ SparseArray<SparseIntArray> uidAppOpModes) {
+ SparseArray<Map<Integer, Map<String, Integer>>> userAppIdAppOpModes = new SparseArray<>();
+
+ int size = uidAppOpModes.size();
+ for (int uidIndex = 0; uidIndex < size; uidIndex++) {
+ int uid = uidAppOpModes.keyAt(uidIndex);
+ int userId = UserHandle.getUserId(uid);
+ Map<Integer, Map<String, Integer>> appIdAppOpModes = userAppIdAppOpModes.get(userId);
+ if (appIdAppOpModes == null) {
+ appIdAppOpModes = new ArrayMap<>();
+ userAppIdAppOpModes.put(userId, appIdAppOpModes);
+ }
+
+ SparseIntArray appOpModes = uidAppOpModes.valueAt(uidIndex);
+ appIdAppOpModes.put(UserHandle.getAppId(uid), getAppOpModesForOpName(appOpModes));
+ }
+ return userAppIdAppOpModes;
+ }
+
+ private SparseArray<Map<String, Map<String, Integer>>> getPackageAppOpModes(
+ SparseArray<ArrayMap<String, SparseIntArray>> legacyPackageAppOpModes) {
+ SparseArray<Map<String, Map<String, Integer>>> userPackageAppOpModes = new SparseArray<>();
+
+ int usersSize = legacyPackageAppOpModes.size();
+ for (int userIndex = 0; userIndex < usersSize; userIndex++) {
+ int userId = legacyPackageAppOpModes.keyAt(userIndex);
+ Map<String, Map<String, Integer>> packageAppOpModes = userPackageAppOpModes.get(userId);
+ if (packageAppOpModes == null) {
+ packageAppOpModes = new ArrayMap<>();
+ userPackageAppOpModes.put(userId, packageAppOpModes);
+ }
+
+ ArrayMap<String, SparseIntArray> legacyPackagesModes =
+ legacyPackageAppOpModes.valueAt(userIndex);
+
+ int packagesSize = legacyPackagesModes.size();
+ for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
+ String packageName = legacyPackagesModes.keyAt(packageIndex);
+ SparseIntArray modes = legacyPackagesModes.valueAt(packageIndex);
+ packageAppOpModes.put(packageName, getAppOpModesForOpName(modes));
+ }
+ }
+ return userPackageAppOpModes;
+ }
+
+ /**
+ * Converts the map from op code -> mode to op name -> mode.
+ */
+ private Map<String, Integer> getAppOpModesForOpName(SparseIntArray appOpCodeModes) {
+ int modesSize = appOpCodeModes.size();
+ Map<String, Integer> appOpNameModes = new ArrayMap<>(modesSize);
+
+ for (int modeIndex = 0; modeIndex < modesSize; modeIndex++) {
+ int opCode = appOpCodeModes.keyAt(modeIndex);
+ int opMode = appOpCodeModes.valueAt(modeIndex);
+ appOpNameModes.put(AppOpsManager.opToName(opCode), opMode);
+ }
+ return appOpNameModes;
+ }
+
+ @Override
+ public int getLegacyAppOpVersion() {
+ synchronized (mLock) {
+ if (mAppIdAppOpModes == null || mPackageAppOpModes == null) {
+ readLegacyAppOpState();
+ }
+ }
+ return mVersionAtBoot;
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index cb2c5434..886add3 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -19,7 +19,6 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
-import static android.app.AppOpsManager.opToDefaultMode;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -30,7 +29,6 @@
import android.content.pm.UserPackage;
import android.os.AsyncTask;
import android.os.Handler;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
@@ -41,25 +39,17 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.XmlUtils;
-import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-
/**
* Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
* In the future this class will also include mode callbacks and op restrictions.
@@ -111,6 +101,8 @@
@GuardedBy("mLock")
final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
+ private final LegacyAppOpStateParser mAppOpsStateParser = new LegacyAppOpStateParser();
+
final AtomicFile mFile;
final Runnable mWriteRunner = new Runnable() {
public void run() {
@@ -502,58 +494,7 @@
public void readState() {
synchronized (mFile) {
synchronized (mLock) {
- FileInputStream stream;
- try {
- stream = mFile.openRead();
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
- mVersionAtBoot = NO_FILE_VERSION;
- return;
- }
-
- try {
- TypedXmlPullParser parser = Xml.resolvePullParser(stream);
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Parse next until we reach the start or end
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new IllegalStateException("no start tag found");
- }
-
- mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
-
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("pkg")) {
- // version 2 has the structure pkg -> uid -> op ->
- // in version 3, since pkg and uid states are kept completely
- // independent we switch to user -> pkg -> op
- readPackage(parser);
- } else if (tagName.equals("uid")) {
- readUidOps(parser);
- } else if (tagName.equals("user")) {
- readUser(parser);
- } else {
- Slog.w(TAG, "Unknown element under <app-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- return;
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ mVersionAtBoot = mAppOpsStateParser.readState(mFile, mUidModes, mUserPackageModes);
}
}
}
@@ -575,162 +516,6 @@
}
@GuardedBy("mLock")
- private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
- XmlPullParserException, IOException {
- final int uid = parser.getAttributeInt(null, "n");
- SparseIntArray modes = mUidModes.get(uid);
- if (modes == null) {
- modes = new SparseIntArray();
- mUidModes.put(uid, modes);
- }
-
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- final int code = parser.getAttributeInt(null, "n");
- final int mode = parser.getAttributeInt(null, "m");
-
- if (mode != opToDefaultMode(code)) {
- modes.put(code, mode);
- }
- } else {
- Slog.w(TAG, "Unknown element under <uid>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- /*
- * Used for migration when pkg is the depth=1 tag
- */
- @GuardedBy("mLock")
- private void readPackage(TypedXmlPullParser parser)
- throws NumberFormatException, XmlPullParserException, IOException {
- String pkgName = parser.getAttributeValue(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("uid")) {
- readUid(parser, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- /*
- * Used for migration when uid is the depth=2 tag
- */
- @GuardedBy("mLock")
- private void readUid(TypedXmlPullParser parser, String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- readOp(parser, userId, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- @GuardedBy("mLock")
- private void readUser(TypedXmlPullParser parser)
- throws NumberFormatException, XmlPullParserException, IOException {
- int userId = parser.getAttributeInt(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("pkg")) {
- readPackage(parser, userId);
- } else {
- Slog.w(TAG, "Unknown element under <user>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- @GuardedBy("mLock")
- private void readPackage(TypedXmlPullParser parser, int userId)
- throws NumberFormatException, XmlPullParserException, IOException {
- String pkgName = parser.getAttributeValue(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- readOp(parser, userId, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- @GuardedBy("mLock")
- private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName)
- throws NumberFormatException, XmlPullParserException {
- final int opCode = parser.getAttributeInt(null, "n");
- final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
- final int mode = parser.getAttributeInt(null, "m", defaultMode);
-
- if (mode != defaultMode) {
- ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
- if (packageModes == null) {
- packageModes = new ArrayMap<>();
- mUserPackageModes.put(userId, packageModes);
- }
-
- SparseIntArray modes = packageModes.get(pkgName);
- if (modes == null) {
- modes = new SparseIntArray();
- packageModes.put(pkgName, modes);
- }
-
- modes.put(opCode, mode);
- }
- }
-
- @GuardedBy("mLock")
private void upgradeLocked(int oldVersion) {
if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
return;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 018db17..0be69ce 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1567,19 +1567,20 @@
}
private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
- final int callingUid = Binder.getCallingUid();
// We get to access everything
- if (callingUid == Process.myPid()) {
+ final int callingPid = Binder.getCallingPid();
+ if (callingPid == Process.myPid()) {
return;
}
// Apps can access their own data
+ final int callingUid = Binder.getCallingUid();
if (uid == callingUid && packageName != null
&& checkPackage(uid, packageName) == MODE_ALLOWED) {
return;
}
// Otherwise, you need a permission...
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), callingUid, null);
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, callingPid,
+ callingUid, null);
}
/**
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
new file mode 100644
index 0000000..a6d5050
--- /dev/null
+++ b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
@@ -0,0 +1,255 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.opToDefaultMode;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+class LegacyAppOpStateParser {
+ static final String TAG = LegacyAppOpStateParser.class.getSimpleName();
+
+ private static final int NO_FILE_VERSION = -2;
+ private static final int NO_VERSION = -1;
+
+ /**
+ * Reads legacy app-ops data into given maps.
+ */
+ public int readState(AtomicFile file, SparseArray<SparseIntArray> uidModes,
+ SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes) {
+ FileInputStream stream;
+ try {
+ stream = file.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
+ return NO_FILE_VERSION;
+ }
+
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Parse next until we reach the start or end
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int versionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ // version 2 has the structure pkg -> uid -> op ->
+ // in version 3, since pkg and uid states are kept completely
+ // independent we switch to user -> pkg -> op
+ readPackage(parser, userPackageModes);
+ } else if (tagName.equals("uid")) {
+ readUidOps(parser, uidModes);
+ } else if (tagName.equals("user")) {
+ readUser(parser, userPackageModes);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ return versionAtBoot;
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void readPackage(TypedXmlPullParser parser,
+ SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("uid")) {
+ readPackageUid(parser, pkgName, userPackageModes);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readPackageUid(TypedXmlPullParser parser, String pkgName,
+ SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ readOp(parser, userId, pkgName, userPackageModes);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readUidOps(TypedXmlPullParser parser, SparseArray<SparseIntArray> uidModes)
+ throws NumberFormatException,
+ XmlPullParserException, IOException {
+ final int uid = parser.getAttributeInt(null, "n");
+ SparseIntArray modes = uidModes.get(uid);
+ if (modes == null) {
+ modes = new SparseIntArray();
+ uidModes.put(uid, modes);
+ }
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ final int code = parser.getAttributeInt(null, "n");
+ final int mode = parser.getAttributeInt(null, "m");
+
+ if (mode != opToDefaultMode(code)) {
+ modes.put(code, mode);
+ }
+ } else {
+ Slog.w(TAG, "Unknown element under <uid>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readUser(TypedXmlPullParser parser,
+ SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int userId = parser.getAttributeInt(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ readPackageOp(parser, userId, userPackageModes);
+ } else {
+ Slog.w(TAG, "Unknown element under <user>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ // read package tag refactored in Android U
+ private void readPackageOp(TypedXmlPullParser parser, int userId,
+ SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ readOp(parser, userId, pkgName, userPackageModes);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName,
+ SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes)
+ throws NumberFormatException, XmlPullParserException {
+ final int opCode = parser.getAttributeInt(null, "n");
+ final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
+ final int mode = parser.getAttributeInt(null, "m", defaultMode);
+
+ if (mode != defaultMode) {
+ ArrayMap<String, SparseIntArray> packageModes = userPackageModes.get(userId);
+ if (packageModes == null) {
+ packageModes = new ArrayMap<>();
+ userPackageModes.put(userId, packageModes);
+ }
+
+ SparseIntArray modes = packageModes.get(pkgName);
+ if (modes == null) {
+ modes = new SparseIntArray();
+ packageModes.put(pkgName, modes);
+ }
+
+ modes.put(opCode, mode);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
new file mode 100644
index 0000000..f0e4b0f5
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+final class HardwareKeyboardShortcutController {
+ @GuardedBy("ImfLock.class")
+ private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
+
+ @GuardedBy("ImfLock.class")
+ void reset(@NonNull InputMethodUtils.InputMethodSettings settings) {
+ mSubtypeHandles.clear();
+ for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
+ if (!imi.shouldShowInInputMethodPicker()) {
+ continue;
+ }
+ final List<InputMethodSubtype> subtypes =
+ settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+ if (subtypes.isEmpty()) {
+ mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null));
+ } else {
+ for (final InputMethodSubtype subtype : subtypes) {
+ if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
+ mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, subtype));
+ }
+ }
+ }
+ }
+ }
+
+ @AnyThread
+ @Nullable
+ static <T> T getNeighborItem(@NonNull List<T> list, @NonNull T value, boolean next) {
+ final int size = list.size();
+ for (int i = 0; i < size; ++i) {
+ if (Objects.equals(value, list.get(i))) {
+ final int nextIndex = (i + (next ? 1 : -1) + size) % size;
+ return list.get(nextIndex);
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ InputMethodSubtypeHandle onSubtypeSwitch(
+ @NonNull InputMethodSubtypeHandle currentImeAndSubtype, boolean forward) {
+ return getNeighborItem(mSubtypeHandles, currentImeAndSubtype, forward);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b440208..2433211 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -316,6 +316,8 @@
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
final InputMethodSubtypeSwitchingController mSwitchingController;
+ final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
+ new HardwareKeyboardShortcutController();
/**
* Tracks how many times {@link #mMethodMap} was updated.
@@ -1731,6 +1733,7 @@
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
+ mHardwareKeyboardShortcutController.reset(mSettings);
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -3268,6 +3271,7 @@
// TODO: Make sure that mSwitchingController and mSettings are sharing the
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
+ mHardwareKeyboardShortcutController.reset(mSettings);
sendOnNavButtonFlagsChangedLocked();
}
@@ -5293,6 +5297,7 @@
// TODO: Make sure that mSwitchingController and mSettings are sharing the
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
+ mHardwareKeyboardShortcutController.reset(mSettings);
sendOnNavButtonFlagsChangedLocked();
@@ -5827,10 +5832,37 @@
@Override
public void switchKeyboardLayout(int direction) {
synchronized (ImfLock.class) {
- if (direction > 0) {
- switchToNextInputMethodLocked(null /* token */, true /* onlyCurrentIme */);
- } else {
- // TODO(b/258853866): Support backwards switching.
+ final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+ if (currentImi == null) {
+ return;
+ }
+ final InputMethodSubtypeHandle currentSubtypeHandle =
+ InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
+ final InputMethodSubtypeHandle nextSubtypeHandle =
+ mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
+ direction > 0);
+ if (nextSubtypeHandle == null) {
+ return;
+ }
+ final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+ if (nextImi == null) {
+ return;
+ }
+
+ final int subtypeCount = nextImi.getSubtypeCount();
+ if (subtypeCount == 0) {
+ if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
+ setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+ }
+ return;
+ }
+
+ for (int i = 0; i < subtypeCount; ++i) {
+ if (nextSubtypeHandle.equals(
+ InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
+ setInputMethodLocked(nextImi.getId(), i);
+ return;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index efa80b7..34b8e65 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1562,6 +1562,18 @@
}
ipw.decreaseIndent();
+ ipw.println("Historical Aggregate Gnss Measurement Provider Data:");
+ ipw.increaseIndent();
+ ArrayMap<CallerIdentity, LocationEventLog.GnssMeasurementAggregateStats>
+ gnssAggregateStats = EVENT_LOG.copyGnssMeasurementAggregateStats();
+ for (int i = 0; i < gnssAggregateStats.size(); i++) {
+ ipw.print(gnssAggregateStats.keyAt(i));
+ ipw.print(": ");
+ gnssAggregateStats.valueAt(i).updateTotals();
+ ipw.println(gnssAggregateStats.valueAt(i));
+ }
+ ipw.decreaseIndent();
+
if (mGnssManagerService != null) {
ipw.println("GNSS Manager:");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index cb952ed..87e193f 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -30,6 +30,7 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.annotation.Nullable;
+import android.location.GnssMeasurementRequest;
import android.location.LocationRequest;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -58,21 +59,25 @@
private static int getLocationsLogSize() {
if (D) {
- return 200;
+ return 400;
} else {
- return 100;
+ return 200;
}
}
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
+ @GuardedBy("mGnssMeasAggregateStats")
+ private final ArrayMap<CallerIdentity, GnssMeasurementAggregateStats> mGnssMeasAggregateStats;
+
@GuardedBy("this")
private final LocationsEventLog mLocationsLog;
private LocationEventLog() {
super(getLogSize(), Object.class);
mAggregateStats = new ArrayMap<>(4);
+ mGnssMeasAggregateStats = new ArrayMap<>();
mLocationsLog = new LocationsEventLog(getLocationsLogSize());
}
@@ -105,6 +110,29 @@
}
}
+ /** Copies out gnss measurement aggregated stats. */
+ public ArrayMap<CallerIdentity, GnssMeasurementAggregateStats>
+ copyGnssMeasurementAggregateStats() {
+ synchronized (mGnssMeasAggregateStats) {
+ ArrayMap<CallerIdentity, GnssMeasurementAggregateStats> copy = new ArrayMap<>(
+ mGnssMeasAggregateStats);
+ return copy;
+ }
+ }
+
+ private GnssMeasurementAggregateStats getGnssMeasurementAggregateStats(
+ CallerIdentity identity) {
+ synchronized (mGnssMeasAggregateStats) {
+ CallerIdentity aggregate = CallerIdentity.forAggregation(identity);
+ GnssMeasurementAggregateStats stats = mGnssMeasAggregateStats.get(aggregate);
+ if (stats == null) {
+ stats = new GnssMeasurementAggregateStats();
+ mGnssMeasAggregateStats.put(aggregate, stats);
+ }
+ return stats;
+ }
+ }
+
/** Logs a user switched event. */
public void logUserSwitched(int userIdFrom, int userIdTo) {
addLog(new UserSwitchedEvent(userIdFrom, userIdTo));
@@ -221,6 +249,29 @@
addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode));
}
+ /** Logs a new client registration for a GNSS Measurement. */
+ public void logGnssMeasurementClientRegistered(CallerIdentity identity,
+ GnssMeasurementRequest request) {
+ addLog(new GnssMeasurementClientRegisterEvent(true, identity, request));
+ getGnssMeasurementAggregateStats(identity).markRequestAdded(request.getIntervalMillis(),
+ request.isFullTracking());
+ }
+
+ /** Logs a new client unregistration for a GNSS Measurement. */
+ public void logGnssMeasurementClientUnregistered(CallerIdentity identity) {
+ addLog(new GnssMeasurementClientRegisterEvent(false, identity, null));
+ getGnssMeasurementAggregateStats(identity).markRequestRemoved();
+ }
+
+ /** Logs a GNSS measurement event deliver for a client. */
+ public void logGnssMeasurementsDelivered(int numGnssMeasurements,
+ CallerIdentity identity) {
+ synchronized (this) {
+ mLocationsLog.logDeliveredGnssMeasurements(numGnssMeasurements, identity);
+ }
+ getGnssMeasurementAggregateStats(identity).markGnssMeasurementDelivered();
+ }
+
private void addLog(Object logEvent) {
addLog(SystemClock.elapsedRealtime(), logEvent);
}
@@ -528,6 +579,50 @@
}
}
+ private static final class GnssMeasurementClientRegisterEvent{
+
+ private final boolean mRegistered;
+ private final CallerIdentity mIdentity;
+ @Nullable
+ private final GnssMeasurementRequest mGnssMeasurementRequest;
+
+ GnssMeasurementClientRegisterEvent(boolean registered,
+ CallerIdentity identity, @Nullable GnssMeasurementRequest measurementRequest) {
+ mRegistered = registered;
+ mIdentity = identity;
+ mGnssMeasurementRequest = measurementRequest;
+ }
+
+ @Override
+ public String toString() {
+ if (mRegistered) {
+ return "gnss measurements +registration " + mIdentity + " -> "
+ + mGnssMeasurementRequest;
+ } else {
+ return "gnss measurements -registration " + mIdentity;
+ }
+ }
+ }
+
+ private static final class GnssMeasurementDeliverEvent {
+
+ private final int mNumGnssMeasurements;
+ @Nullable
+ private final CallerIdentity mIdentity;
+
+ GnssMeasurementDeliverEvent(int numGnssMeasurements,
+ @Nullable CallerIdentity identity) {
+ mNumGnssMeasurements = numGnssMeasurements;
+ mIdentity = identity;
+ }
+
+ @Override
+ public String toString() {
+ return "gnss measurements delivered GnssMeasurements[" + mNumGnssMeasurements + "]"
+ + " to " + mIdentity;
+ }
+ }
+
private static final class LocationsEventLog extends LocalEventLog<Object> {
LocationsEventLog(int size) {
@@ -538,6 +633,11 @@
addLog(new ProviderReceiveLocationEvent(provider, numLocations));
}
+ public void logDeliveredGnssMeasurements(int numGnssMeasurements,
+ CallerIdentity identity) {
+ addLog(new GnssMeasurementDeliverEvent(numGnssMeasurements, identity));
+ }
+
public void logProviderDeliveredLocations(String provider, int numLocations,
CallerIdentity identity) {
addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity));
@@ -668,4 +768,89 @@
}
}
}
+
+ /**
+ * Aggregate statistics for GNSS measurements.
+ */
+ public static final class GnssMeasurementAggregateStats {
+ @GuardedBy("this")
+ private int mAddedRequestCount;
+ @GuardedBy("this")
+ private int mReceivedMeasurementEventCount;
+ @GuardedBy("this")
+ private long mAddedTimeTotalMs;
+ @GuardedBy("this")
+ private long mAddedTimeLastUpdateRealtimeMs;
+ @GuardedBy("this")
+ private long mFastestIntervalMs = Long.MAX_VALUE;
+ @GuardedBy("this")
+ private long mSlowestIntervalMs = 0;
+ @GuardedBy("this")
+ private boolean mHasFullTracking;
+ @GuardedBy("this")
+ private boolean mHasDutyCycling;
+
+ GnssMeasurementAggregateStats() {
+ }
+
+ synchronized void markRequestAdded(long intervalMillis, boolean fullTracking) {
+ if (mAddedRequestCount++ == 0) {
+ mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime();
+ }
+ if (fullTracking) {
+ mHasFullTracking = true;
+ } else {
+ mHasDutyCycling = true;
+ }
+ mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs);
+ mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs);
+ }
+
+ synchronized void markRequestRemoved() {
+ updateTotals();
+ --mAddedRequestCount;
+ Preconditions.checkState(mAddedRequestCount >= 0);
+ }
+
+ synchronized void markGnssMeasurementDelivered() {
+ mReceivedMeasurementEventCount++;
+ }
+
+ public synchronized void updateTotals() {
+ if (mAddedRequestCount > 0) {
+ long realtimeMs = SystemClock.elapsedRealtime();
+ mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs;
+ mAddedTimeLastUpdateRealtimeMs = realtimeMs;
+ }
+ }
+
+ @Override
+ public synchronized String toString() {
+ return "min/max interval = "
+ + intervalToString(mFastestIntervalMs) + "/"
+ + intervalToString(mSlowestIntervalMs)
+ + ", total duration = " + formatDuration(mAddedTimeTotalMs)
+ + ", tracking mode = " + trackingModeToString() + ", GNSS measurement events = "
+ + mReceivedMeasurementEventCount;
+ }
+
+ private static String intervalToString(long intervalMs) {
+ if (intervalMs == GnssMeasurementRequest.PASSIVE_INTERVAL) {
+ return "passive";
+ } else {
+ return MILLISECONDS.toSeconds(intervalMs) + "s";
+ }
+ }
+
+ @GuardedBy("this")
+ private String trackingModeToString() {
+ if (mHasFullTracking && mHasDutyCycling) {
+ return "mixed tracking mode";
+ } else if (mHasFullTracking) {
+ return "always full-tracking";
+ } else {
+ return "always duty-cycling";
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 041f11d..d02b6f4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -18,6 +18,7 @@
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static com.android.server.location.gnss.GnssManagerService.D;
import static com.android.server.location.gnss.GnssManagerService.TAG;
@@ -62,11 +63,17 @@
@Override
protected void onRegister() {
super.onRegister();
-
+ EVENT_LOG.logGnssMeasurementClientRegistered(getIdentity(), getRequest());
executeOperation(listener -> listener.onStatusChanged(
GnssMeasurementsEvent.Callback.STATUS_READY));
}
+ @Override
+ protected void onUnregister() {
+ EVENT_LOG.logGnssMeasurementClientUnregistered(getIdentity());
+ super.onUnregister();
+ }
+
@Nullable
@Override
protected void onActive() {
@@ -250,6 +257,8 @@
deliverToListeners(registration -> {
if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION,
registration.getIdentity())) {
+ EVENT_LOG.logGnssMeasurementsDelivered(event.getMeasurements().size(),
+ registration.getIdentity());
return listener -> listener.onGnssMeasurementsReceived(event);
} else {
return null;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 71f8e16..46c2594 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -656,7 +656,6 @@
private ConditionProviders mConditionProviders;
private NotificationUsageStats mUsageStats;
private boolean mLockScreenAllowSecureNotifications = true;
- boolean mAllowFgsDismissal = false;
boolean mSystemExemptFromDismissal = false;
private static final int MY_UID = Process.myUid();
@@ -2581,19 +2580,9 @@
for (String name : properties.getKeyset()) {
if (SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE.equals(name)) {
mAssistants.resetDefaultAssistantsIfNecessary();
- } else if (SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED.equals(name)) {
- String value = properties.getString(name, null);
- if ("true".equals(value)) {
- mAllowFgsDismissal = true;
- } else if ("false".equals(value)) {
- mAllowFgsDismissal = false;
- }
}
}
};
- mAllowFgsDismissal = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, true);
mSystemExemptFromDismissal = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
/* name= */ "application_exemptions",
@@ -6727,8 +6716,11 @@
handleSavePolicyFile();
}
- private void makeStickyHun(Notification notification) {
- notification.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
+ private void makeStickyHun(Notification notification, String pkg, @UserIdInt int userId) {
+ if (mPermissionHelper.hasRequestedPermission(
+ Manifest.permission.USE_FULL_SCREEN_INTENT, pkg, userId)) {
+ notification.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
+ }
if (notification.contentIntent == null) {
// On notification click, if contentIntent is null, SystemUI launches the
// fullScreenIntent instead.
@@ -6792,10 +6784,9 @@
SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
if (forceDemoteFsiToStickyHun) {
- makeStickyHun(notification);
+ makeStickyHun(notification, pkg, userId);
} else if (showStickyHunIfDenied) {
-
final AttributionSource source = new AttributionSource.Builder(notificationUid)
.setPackageName(pkg)
.build();
@@ -6804,7 +6795,7 @@
Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
if (permissionResult != PermissionManager.PERMISSION_GRANTED) {
- makeStickyHun(notification);
+ makeStickyHun(notification, pkg, userId);
}
} else {
@@ -7733,9 +7724,6 @@
// flags are set.
if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= FLAG_NO_CLEAR;
- if (!mAllowFgsDismissal) {
- notification.flags |= FLAG_ONGOING_EVENT;
- }
}
mRankingHelper.extractSignals(r);
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index e6fd7ec..b6fd822 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -78,6 +78,30 @@
}
/**
+ * Returns whether the given app requested the given permission. Must not be called
+ * with a lock held.
+ */
+ public boolean hasRequestedPermission(String permission, String pkg, @UserIdInt int userId) {
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ PackageInfo pi = mPackageManager.getPackageInfo(pkg, GET_PERMISSIONS, userId);
+ if (pi == null || pi.requestedPermissions == null) {
+ return false;
+ }
+ for (String perm : pi.requestedPermissions) {
+ if (permission.equals(perm)) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Could not reach system server", e);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ return false;
+ }
+
+ /**
* Returns all of the apps that have requested the notification permission in a given user.
* Must not be called with a lock held. Format: uid, packageName
*/
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 20e5d39..7603c45 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -278,6 +278,7 @@
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
static final int WRITE_USER_MSG = 1;
+ static final int WRITE_USER_LIST_MSG = 2;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
@@ -321,7 +322,6 @@
private final Handler mHandler;
private final File mUsersDir;
- @GuardedBy("mPackagesLock")
private final File mUserListFile;
private final IBinder mUserRestrictionToken = new Binder();
@@ -3640,77 +3640,95 @@
mUpdatingSystemUserMode = true;
}
+
+ private ResilientAtomicFile getUserListFile() {
+ File tempBackup = new File(mUserListFile.getParent(), mUserListFile.getName() + ".backup");
+ File reserveCopy = new File(mUserListFile.getParent(),
+ mUserListFile.getName() + ".reservecopy");
+ int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+ return new ResilientAtomicFile(mUserListFile, tempBackup, reserveCopy, fileMode,
+ "user list", (priority, msg) -> {
+ Slog.e(LOG_TAG, msg);
+ // Something went wrong, schedule full rewrite.
+ scheduleWriteUserList();
+ });
+ }
+
@GuardedBy({"mPackagesLock"})
private void readUserListLP() {
- if (!mUserListFile.exists()) {
- fallbackToSingleUserLP();
- return;
- }
- FileInputStream fis = null;
- AtomicFile userListFile = new AtomicFile(mUserListFile);
- try {
- fis = userListFile.openRead();
- final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Skip
- }
+ try (ResilientAtomicFile file = getUserListFile()) {
+ FileInputStream fin = null;
+ try {
+ fin = file.openRead();
+ if (fin == null) {
+ Slog.e(LOG_TAG, "userlist.xml not found, fallback to single user");
+ fallbackToSingleUserLP();
+ return;
+ }
- if (type != XmlPullParser.START_TAG) {
- Slog.e(LOG_TAG, "Unable to read user list");
- fallbackToSingleUserLP();
- return;
- }
+ final TypedXmlPullParser parser = Xml.resolvePullParser(fin);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Skip
+ }
- mNextSerialNumber = -1;
- if (parser.getName().equals(TAG_USERS)) {
- mNextSerialNumber =
- parser.getAttributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
- mUserVersion =
- parser.getAttributeInt(null, ATTR_USER_VERSION, mUserVersion);
- mUserTypeVersion =
- parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
- }
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(LOG_TAG, "Unable to read user list");
+ fallbackToSingleUserLP();
+ return;
+ }
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (type == XmlPullParser.START_TAG) {
- final String name = parser.getName();
- if (name.equals(TAG_USER)) {
- UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+ mNextSerialNumber = -1;
+ if (parser.getName().equals(TAG_USERS)) {
+ mNextSerialNumber =
+ parser.getAttributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
+ mUserVersion =
+ parser.getAttributeInt(null, ATTR_USER_VERSION, mUserVersion);
+ mUserTypeVersion =
+ parser.getAttributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
+ }
- if (userData != null) {
- synchronized (mUsersLock) {
- mUsers.put(userData.info.id, userData);
- if (mNextSerialNumber < 0
- || mNextSerialNumber <= userData.info.id) {
- mNextSerialNumber = userData.info.id + 1;
- }
- }
- }
- } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && type != XmlPullParser.END_TAG) {
- if (type == XmlPullParser.START_TAG) {
- if (parser.getName().equals(TAG_RESTRICTIONS)) {
- synchronized (mGuestRestrictions) {
- UserRestrictionsUtils
- .readRestrictions(parser, mGuestRestrictions);
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ final String name = parser.getName();
+ if (name.equals(TAG_USER)) {
+ UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+
+ if (userData != null) {
+ synchronized (mUsersLock) {
+ mUsers.put(userData.info.id, userData);
+ if (mNextSerialNumber < 0
+ || mNextSerialNumber <= userData.info.id) {
+ mNextSerialNumber = userData.info.id + 1;
}
}
- break;
+ }
+ } else if (name.equals(TAG_GUEST_RESTRICTIONS)) {
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.END_TAG) {
+ if (type == XmlPullParser.START_TAG) {
+ if (parser.getName().equals(TAG_RESTRICTIONS)) {
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils
+ .readRestrictions(parser, mGuestRestrictions);
+ }
+ }
+ break;
+ }
}
}
}
}
- }
- updateUserIds();
- upgradeIfNecessaryLP();
- } catch (IOException | XmlPullParserException e) {
- fallbackToSingleUserLP();
- } finally {
- IoUtils.closeQuietly(fis);
+ updateUserIds();
+ upgradeIfNecessaryLP();
+ } catch (Exception e) {
+ // Remove corrupted file and retry.
+ file.failRead(fin, e);
+ readUserListLP();
+ return;
+ }
}
synchronized (mUsersLock) {
@@ -4116,6 +4134,18 @@
}
}
+ private void scheduleWriteUserList() {
+ if (DBG) {
+ debug("scheduleWriteUserList");
+ }
+ // No need to wrap it within a lock -- worst case, we'll just post the same message
+ // twice.
+ if (!mHandler.hasMessages(WRITE_USER_LIST_MSG)) {
+ Message msg = mHandler.obtainMessage(WRITE_USER_LIST_MSG);
+ mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
+ }
+ }
+
private void scheduleWriteUser(UserData userData) {
if (DBG) {
debug("scheduleWriteUser");
@@ -4128,20 +4158,37 @@
}
}
+ private ResilientAtomicFile getUserFile(int userId) {
+ File file = new File(mUsersDir, userId + XML_SUFFIX);
+ File tempBackup = new File(mUsersDir, userId + XML_SUFFIX + ".backup");
+ File reserveCopy = new File(mUsersDir, userId + XML_SUFFIX + ".reservecopy");
+ int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+ return new ResilientAtomicFile(file, tempBackup, reserveCopy, fileMode,
+ "user info", (priority, msg) -> {
+ Slog.e(LOG_TAG, msg);
+ // Something went wrong, schedule full rewrite.
+ UserData userData = getUserDataNoChecks(userId);
+ if (userData != null) {
+ scheduleWriteUser(userData);
+ }
+ });
+ }
+
@GuardedBy({"mPackagesLock"})
private void writeUserLP(UserData userData) {
if (DBG) {
debug("writeUserLP " + userData);
}
- FileOutputStream fos = null;
- AtomicFile userFile = new AtomicFile(new File(mUsersDir, userData.info.id + XML_SUFFIX));
- try {
- fos = userFile.startWrite();
- writeUserLP(userData, fos);
- userFile.finishWrite(fos);
- } catch (Exception ioe) {
- Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
- userFile.failWrite(fos);
+ try (ResilientAtomicFile userFile = getUserFile(userData.info.id)) {
+ FileOutputStream fos = null;
+ try {
+ fos = userFile.startWrite();
+ writeUserLP(userData, fos);
+ userFile.finishWrite(fos);
+ } catch (Exception ioe) {
+ Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
+ userFile.failWrite(fos);
+ }
}
}
@@ -4270,65 +4317,71 @@
if (DBG) {
debug("writeUserList");
}
- FileOutputStream fos = null;
- AtomicFile userListFile = new AtomicFile(mUserListFile);
- try {
- fos = userListFile.startWrite();
- final TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
- serializer.startTag(null, TAG_USERS);
- serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
- serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion);
- serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
+ try (ResilientAtomicFile file = getUserListFile()) {
+ FileOutputStream fos = null;
+ try {
+ fos = file.startWrite();
- serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
- synchronized (mGuestRestrictions) {
- UserRestrictionsUtils
- .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
- }
- serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
- int[] userIdsToWrite;
- synchronized (mUsersLock) {
- userIdsToWrite = new int[mUsers.size()];
- for (int i = 0; i < userIdsToWrite.length; i++) {
- UserInfo user = mUsers.valueAt(i).info;
- userIdsToWrite[i] = user.id;
+ final TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
+ true);
+
+ serializer.startTag(null, TAG_USERS);
+ serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
+ serializer.attributeInt(null, ATTR_USER_VERSION, mUserVersion);
+ serializer.attributeInt(null, ATTR_USER_TYPE_VERSION, mUserTypeVersion);
+
+ serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
+ synchronized (mGuestRestrictions) {
+ UserRestrictionsUtils
+ .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
}
- }
- for (int id : userIdsToWrite) {
- serializer.startTag(null, TAG_USER);
- serializer.attributeInt(null, ATTR_ID, id);
- serializer.endTag(null, TAG_USER);
- }
+ serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+ int[] userIdsToWrite;
+ synchronized (mUsersLock) {
+ userIdsToWrite = new int[mUsers.size()];
+ for (int i = 0; i < userIdsToWrite.length; i++) {
+ UserInfo user = mUsers.valueAt(i).info;
+ userIdsToWrite[i] = user.id;
+ }
+ }
+ for (int id : userIdsToWrite) {
+ serializer.startTag(null, TAG_USER);
+ serializer.attributeInt(null, ATTR_ID, id);
+ serializer.endTag(null, TAG_USER);
+ }
- serializer.endTag(null, TAG_USERS);
+ serializer.endTag(null, TAG_USERS);
- serializer.endDocument();
- userListFile.finishWrite(fos);
- } catch (Exception e) {
- userListFile.failWrite(fos);
- Slog.e(LOG_TAG, "Error writing user list");
+ serializer.endDocument();
+ file.finishWrite(fos);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error writing user list", e);
+ file.failWrite(fos);
+ }
}
}
@GuardedBy({"mPackagesLock"})
private UserData readUserLP(int id) {
- FileInputStream fis = null;
- try {
- AtomicFile userFile =
- new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
- fis = userFile.openRead();
- return readUserLP(id, fis);
- } catch (IOException ioe) {
- Slog.e(LOG_TAG, "Error reading user list");
- } catch (XmlPullParserException pe) {
- Slog.e(LOG_TAG, "Error reading user list");
- } finally {
- IoUtils.closeQuietly(fis);
+ try (ResilientAtomicFile file = getUserFile(id)) {
+ FileInputStream fis = null;
+ try {
+ fis = file.openRead();
+ if (fis == null) {
+ Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
+ return null;
+ }
+ return readUserLP(id, fis);
+ } catch (Exception e) {
+ // Remove corrupted file and retry.
+ Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
+ file.failRead(fis, e);
+ return readUserLP(id);
+ }
}
- return null;
}
@GuardedBy({"mPackagesLock"})
@@ -5790,9 +5843,8 @@
synchronized (mPackagesLock) {
writeUserListLP();
}
- // Remove user file
- AtomicFile userFile = new AtomicFile(new File(mUsersDir, userId + XML_SUFFIX));
- userFile.delete();
+ // Remove user file(s)
+ getUserFile(userId).delete();
updateUserIds();
if (RELEASE_DELETED_USER_ID) {
synchronized (mUsersLock) {
@@ -6755,6 +6807,13 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
+ case WRITE_USER_LIST_MSG: {
+ removeMessages(WRITE_USER_LIST_MSG);
+ synchronized (mPackagesLock) {
+ writeUserListLP();
+ }
+ break;
+ }
case WRITE_USER_MSG:
removeMessages(WRITE_USER_MSG, msg.obj);
synchronized (mPackagesLock) {
@@ -6767,6 +6826,7 @@
+ ", it was probably removed before handler could handle it");
}
}
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f340f93..c9ebeae 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -652,20 +652,33 @@
private boolean isAdbVerificationEnabled(PackageInfoLite pkgInfoLite, int userId,
boolean requestedDisableVerification) {
+ boolean verifierIncludeAdb = android.provider.Settings.Global.getInt(
+ mPm.mContext.getContentResolver(),
+ android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
+
if (mPm.isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
+ if (!verifierIncludeAdb) {
+ Slog.w(TAG, "Force verification of ADB install because of user restriction.");
+ }
return true;
}
- // Check if the developer wants to skip verification for ADB installs
+
+ // Check if the verification disabled globally, first.
+ if (!verifierIncludeAdb) {
+ return false;
+ }
+
+ // Check if the developer wants to skip verification for ADB installs.
if (requestedDisableVerification) {
if (!packageExists(pkgInfoLite.packageName)) {
- // Always verify fresh install
+ // Always verify fresh install.
return true;
}
- // Only skip when apk is debuggable
+ // Only skip when apk is debuggable.
return !pkgInfoLite.debuggable;
}
- return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
- android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
+
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 572e13c..df0192c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -64,6 +64,7 @@
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.permission.PermissionManagerInternal;
+import android.service.voice.VoiceInteractionManagerInternal;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
@@ -967,12 +968,13 @@
// the private data in your process; or by you explicitly calling to another
// app passing the source, in which case you must trust the other side;
- final int callingUid = Binder.getCallingUid();
- if (source.getUid() != callingUid && mContext.checkPermission(
+ final int callingUid = resolveUid(Binder.getCallingUid());
+ final int sourceUid = resolveUid(source.getUid());
+ if (sourceUid != callingUid && mContext.checkPermission(
Manifest.permission.UPDATE_APP_OPS_STATS, /*pid*/ -1, callingUid)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Cannot register attribution source for uid:"
- + source.getUid() + " from uid:" + callingUid);
+ + sourceUid + " from uid:" + callingUid);
}
final PackageManagerInternal packageManagerInternal = LocalServices.getService(
@@ -981,10 +983,10 @@
// TODO(b/234653108): Clean up this UID/package & cross-user check.
// If calling from the system process, allow registering attribution for package from
// any user
- int userId = UserHandle.getUserId((callingUid == Process.SYSTEM_UID ? source.getUid()
+ int userId = UserHandle.getUserId((callingUid == Process.SYSTEM_UID ? sourceUid
: callingUid));
if (packageManagerInternal.getPackageUid(source.getPackageName(), 0, userId)
- != source.getUid()) {
+ != sourceUid) {
throw new SecurityException("Cannot register attribution source for package:"
+ source.getPackageName() + " from uid:" + callingUid);
}
@@ -1010,6 +1012,21 @@
return false;
}
}
+
+ private int resolveUid(int uid) {
+ final VoiceInteractionManagerInternal vimi = LocalServices
+ .getService(VoiceInteractionManagerInternal.class);
+ if (vimi == null) {
+ return uid;
+ }
+ final VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity
+ hotwordDetectionServiceIdentity = vimi.getHotwordDetectionServiceIdentity();
+ if (hotwordDetectionServiceIdentity != null
+ && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+ return hotwordDetectionServiceIdentity.getOwnerUid();
+ }
+ return uid;
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
index a213268..2ae39b7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionMigrationHelperImpl.java
@@ -92,23 +92,29 @@
packageManagerLocal.withUnfilteredSnapshot()) {
Map<String, PackageState> packageStates = snapshot.getPackageStates();
legacyState.getPackagePermissions().forEach((packageName, permissionStates) -> {
- PackageState packageState = packageStates.get(packageName);
- if (packageState != null) {
- int appId = packageState.getAppId();
- appIdPermissionStates.put(appId, toLegacyPermissionStates(permissionStates));
- } else {
- Log.w(LOG_TAG, "Package " + packageName + " not found.");
+ if (!permissionStates.isEmpty()) {
+ PackageState packageState = packageStates.get(packageName);
+ if (packageState != null) {
+ int appId = packageState.getAppId();
+ appIdPermissionStates.put(appId,
+ toLegacyPermissionStates(permissionStates));
+ } else {
+ Log.w(LOG_TAG, "Package " + packageName + " not found.");
+ }
}
});
Map<String, SharedUserApi> sharedUsers = snapshot.getSharedUsers();
legacyState.getSharedUserPermissions().forEach((sharedUserName, permissionStates) -> {
- SharedUserApi sharedUser = sharedUsers.get(sharedUserName);
- if (sharedUser != null) {
- int appId = sharedUser.getAppId();
- appIdPermissionStates.put(appId, toLegacyPermissionStates(permissionStates));
- } else {
- Log.w(LOG_TAG, "Shared user " + sharedUserName + " not found.");
+ if (!permissionStates.isEmpty()) {
+ SharedUserApi sharedUser = sharedUsers.get(sharedUserName);
+ if (sharedUser != null) {
+ int appId = sharedUser.getAppId();
+ appIdPermissionStates.put(appId,
+ toLegacyPermissionStates(permissionStates));
+ } else {
+ Log.w(LOG_TAG, "Shared user " + sharedUserName + " not found.");
+ }
}
});
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 449d2eb..adaad2a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -731,7 +731,7 @@
mAutofillManagerInternal.onBackKeyPressed();
break;
case MSG_SYSTEM_KEY_PRESS:
- sendSystemKeyToStatusBar(msg.arg1);
+ sendSystemKeyToStatusBar((KeyEvent) msg.obj);
break;
case MSG_HANDLE_ALL_APPS:
launchAllAppsAction();
@@ -951,7 +951,7 @@
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
// Inform the StatusBar; but do not allow it to consume the event.
- sendSystemKeyToStatusBarAsync(event.getKeyCode());
+ sendSystemKeyToStatusBarAsync(event);
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
@@ -3036,7 +3036,11 @@
break;
case KeyEvent.KEYCODE_N:
if (down && event.isMetaPressed()) {
- toggleNotificationPanel();
+ if (event.isCtrlPressed()) {
+ sendSystemKeyToStatusBarAsync(event);
+ } else {
+ toggleNotificationPanel();
+ }
return key_consumed;
}
break;
@@ -3604,14 +3608,16 @@
@Override
public int applyKeyguardOcclusionChange() {
- if (mKeyguardOccludedChanged) {
- if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
- + mPendingKeyguardOccluded);
- if (setKeyguardOccludedLw(mPendingKeyguardOccluded)) {
- return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
- }
+ if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded="
+ + mPendingKeyguardOccluded);
+
+ // TODO(b/276433230): Explicitly save before/after for occlude state in each
+ // Transition so we don't need to update SysUI every time.
+ if (setKeyguardOccludedLw(mPendingKeyguardOccluded)) {
+ return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
+ } else {
+ return 0;
}
- return 0;
}
/**
@@ -3889,6 +3895,7 @@
private boolean setKeyguardOccludedLw(boolean isOccluded) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
mKeyguardOccludedChanged = false;
+ mPendingKeyguardOccluded = isOccluded;
mKeyguardDelegate.setOccluded(isOccluded, true /* notify */);
return mKeyguardDelegate.isShowing();
}
@@ -4154,7 +4161,7 @@
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (down) {
- sendSystemKeyToStatusBarAsync(event.getKeyCode());
+ sendSystemKeyToStatusBarAsync(event);
NotificationManager nm = getNotificationService();
if (nm != null && !mHandleVolumeKeysInWM) {
@@ -4432,7 +4439,7 @@
case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
if (down && mStylusButtonsEnabled) {
- sendSystemKeyToStatusBarAsync(keyCode);
+ sendSystemKeyToStatusBarAsync(event);
}
result &= ~ACTION_PASS_TO_USER;
break;
@@ -4529,7 +4536,7 @@
if (!mAccessibilityManager.isEnabled()
|| !mAccessibilityManager.sendFingerprintGesture(event.getKeyCode())) {
if (mSystemNavigationKeysEnabled) {
- sendSystemKeyToStatusBarAsync(event.getKeyCode());
+ sendSystemKeyToStatusBarAsync(event);
}
}
}
@@ -4538,11 +4545,11 @@
/**
* Notify the StatusBar that a system key was pressed.
*/
- private void sendSystemKeyToStatusBar(int keyCode) {
+ private void sendSystemKeyToStatusBar(KeyEvent key) {
IStatusBarService statusBar = getStatusBarService();
if (statusBar != null) {
try {
- statusBar.handleSystemKey(keyCode);
+ statusBar.handleSystemKey(key);
} catch (RemoteException e) {
// Oh well.
}
@@ -4552,8 +4559,8 @@
/**
* Notify the StatusBar that a system key was pressed without blocking the current thread.
*/
- private void sendSystemKeyToStatusBarAsync(int keyCode) {
- Message message = mHandler.obtainMessage(MSG_SYSTEM_KEY_PRESS, keyCode, 0);
+ private void sendSystemKeyToStatusBarAsync(KeyEvent keyEvent) {
+ Message message = mHandler.obtainMessage(MSG_SYSTEM_KEY_PRESS, keyEvent);
message.setAsynchronous(true);
mHandler.sendMessage(message);
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index a9a1d5e..1a22b89 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -48,7 +48,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -324,16 +323,7 @@
private boolean checkTidValid(int uid, int tgid, int [] tids) {
// Make sure all tids belongs to the same UID (including isolated UID),
// tids can belong to different application processes.
- List<Integer> eligiblePids = null;
- // To avoid deadlock, do not call into AMS if the call is from system.
- if (uid != Process.SYSTEM_UID) {
- eligiblePids = mAmInternal.getIsolatedProcesses(uid);
- }
- if (eligiblePids == null) {
- eligiblePids = new ArrayList<>();
- }
- eligiblePids.add(tgid);
-
+ List<Integer> isolatedPids = null;
for (int threadId : tids) {
final String[] procStatusKeys = new String[] {
"Uid:",
@@ -345,7 +335,21 @@
int pidOfThreadId = (int) output[1];
// use PID check for isolated processes, use UID check for non-isolated processes.
- if (eligiblePids.contains(pidOfThreadId) || uidOfThreadId == uid) {
+ if (pidOfThreadId == tgid || uidOfThreadId == uid) {
+ continue;
+ }
+ // Only call into AM if the tid is either isolated or invalid
+ if (isolatedPids == null) {
+ // To avoid deadlock, do not call into AMS if the call is from system.
+ if (uid == Process.SYSTEM_UID) {
+ return false;
+ }
+ isolatedPids = mAmInternal.getIsolatedProcesses(uid);
+ if (isolatedPids == null) {
+ return false;
+ }
+ }
+ if (isolatedPids.contains(pidOfThreadId)) {
continue;
}
return false;
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index d55fbc2..231ffc6 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -20,6 +20,7 @@
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
+import android.app.ActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -27,11 +28,10 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.IndentingPrintWriter;
-import android.util.IntArray;
-import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeSparseArray;
import android.util.TimeUtils;
@@ -59,7 +59,7 @@
private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
@VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
- private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
+ private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
private final Handler mHandler;
private final IrqDeviceMap mIrqDeviceMap;
@@ -69,10 +69,15 @@
@VisibleForTesting
final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>();
+
+ /* Maps timestamp -> {subsystem -> {uid -> procState}} */
@VisibleForTesting
- final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
+ final TimeSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution =
new TimeSparseArray<>();
+ final SparseIntArray mUidProcStates = new SparseIntArray();
+ private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4);
+
public CpuWakeupStats(Context context, int mapRes, Handler handler) {
mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
mHandler = handler;
@@ -102,13 +107,14 @@
FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN,
FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN,
null,
- wakeupToLog.mElapsedMillis);
+ wakeupToLog.mElapsedMillis,
+ null);
Trace.instantForTrack(Trace.TRACE_TAG_POWER, TRACE_TRACK_WAKEUP_ATTRIBUTION,
wakeupToLog.mElapsedMillis + " --");
return;
}
- final SparseArray<SparseBooleanArray> wakeupAttribution = mWakeupAttribution.get(
+ final SparseArray<SparseIntArray> wakeupAttribution = mWakeupAttribution.get(
wakeupToLog.mElapsedMillis);
if (wakeupAttribution == null) {
// This is not expected but can theoretically happen in extreme situations, e.g. if we
@@ -121,24 +127,28 @@
for (int i = 0; i < wakeupAttribution.size(); i++) {
final int subsystem = wakeupAttribution.keyAt(i);
- final SparseBooleanArray uidMap = wakeupAttribution.valueAt(i);
+ final SparseIntArray uidProcStates = wakeupAttribution.valueAt(i);
final int[] uids;
- if (uidMap == null || uidMap.size() == 0) {
- uids = new int[0];
+ final int[] procStatesProto;
+
+ if (uidProcStates == null || uidProcStates.size() == 0) {
+ uids = procStatesProto = new int[0];
} else {
- final IntArray tmp = new IntArray(uidMap.size());
- for (int j = 0; j < uidMap.size(); j++) {
- if (uidMap.valueAt(j)) {
- tmp.add(uidMap.keyAt(j));
- }
+ final int numUids = uidProcStates.size();
+ uids = new int[numUids];
+ procStatesProto = new int[numUids];
+ for (int j = 0; j < numUids; j++) {
+ uids[j] = uidProcStates.keyAt(j);
+ procStatesProto[j] = ActivityManager.processStateAmToProto(
+ uidProcStates.valueAt(j));
}
- uids = tmp.toArray();
}
FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
subsystemToStatsReason(subsystem),
uids,
- wakeupToLog.mElapsedMillis);
+ wakeupToLog.mElapsedMillis,
+ procStatesProto);
if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
if (i == 0) {
@@ -154,6 +164,20 @@
traceEventBuilder.toString().trim());
}
+ /**
+ * Clean up data for a uid that is being removed.
+ */
+ public synchronized void onUidRemoved(int uid) {
+ mUidProcStates.delete(uid);
+ }
+
+ /**
+ * Notes a procstate change for the given uid to maintain the mapping internally.
+ */
+ public synchronized void noteUidProcessState(int uid, int state) {
+ mUidProcStates.put(uid, state);
+ }
+
/** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
String rawReason) {
@@ -184,8 +208,17 @@
/** Notes a waking activity that could have potentially woken up the CPU. */
public synchronized void noteWakingActivity(int subsystem, long elapsedRealtime, int... uids) {
- if (!attemptAttributionWith(subsystem, elapsedRealtime, uids)) {
- mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime, uids);
+ if (uids == null) {
+ return;
+ }
+ mReusableUidProcStates.clear();
+ for (int i = 0; i < uids.length; i++) {
+ mReusableUidProcStates.put(uids[i],
+ mUidProcStates.get(uids[i], ActivityManager.PROCESS_STATE_UNKNOWN));
+ }
+ if (!attemptAttributionWith(subsystem, elapsedRealtime, mReusableUidProcStates)) {
+ mRecentWakingActivity.recordActivity(subsystem, elapsedRealtime,
+ mReusableUidProcStates);
}
}
@@ -196,7 +229,7 @@
return;
}
- SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
+ SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(wakeup.mElapsedMillis);
if (attribution == null) {
attribution = new SparseArray<>();
mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
@@ -210,14 +243,14 @@
final long startTime = wakeup.mElapsedMillis - WAKEUP_REASON_HALF_WINDOW_MS;
final long endTime = wakeup.mElapsedMillis + WAKEUP_REASON_HALF_WINDOW_MS;
- final SparseBooleanArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
+ final SparseIntArray uidsToBlame = mRecentWakingActivity.removeBetween(subsystem,
startTime, endTime);
attribution.put(subsystem, uidsToBlame);
}
}
private synchronized boolean attemptAttributionWith(int subsystem, long activityElapsed,
- int... uids) {
+ SparseIntArray uidProcStates) {
final int startIdx = mWakeupEvents.closestIndexOnOrAfter(
activityElapsed - WAKEUP_REASON_HALF_WINDOW_MS);
final int endIdx = mWakeupEvents.closestIndexOnOrBefore(
@@ -233,19 +266,19 @@
if (subsystems.get(subsystem)) {
// We don't expect more than one wakeup to be found within such a short window, so
// just attribute this one and exit
- SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+ SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(
wakeup.mElapsedMillis);
if (attribution == null) {
attribution = new SparseArray<>();
mWakeupAttribution.put(wakeup.mElapsedMillis, attribution);
}
- SparseBooleanArray uidsToBlame = attribution.get(subsystem);
+ SparseIntArray uidsToBlame = attribution.get(subsystem);
if (uidsToBlame == null) {
- uidsToBlame = new SparseBooleanArray(uids.length);
- attribution.put(subsystem, uidsToBlame);
- }
- for (final int uid : uids) {
- uidsToBlame.put(uid, true);
+ attribution.put(subsystem, uidProcStates.clone());
+ } else {
+ for (int i = 0; i < uidProcStates.size(); i++) {
+ uidsToBlame.put(uidProcStates.keyAt(i), uidProcStates.valueAt(i));
+ }
}
return true;
}
@@ -267,6 +300,19 @@
mRecentWakingActivity.dump(pw, nowElapsed);
pw.println();
+ pw.println("Current proc-state map (" + mUidProcStates.size() + "):");
+ pw.increaseIndent();
+ for (int i = 0; i < mUidProcStates.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ UserHandle.formatUid(pw, mUidProcStates.keyAt(i));
+ pw.print(":" + ActivityManager.procStateToString(mUidProcStates.valueAt(i)));
+ }
+ pw.println();
+ pw.decreaseIndent();
+ pw.println();
+
final SparseLongArray attributionStats = new SparseLongArray();
pw.println("Wakeup events:");
pw.increaseIndent();
@@ -278,7 +324,7 @@
final Wakeup wakeup = mWakeupEvents.valueAt(i);
pw.println(wakeup);
pw.print("Attribution: ");
- final SparseArray<SparseBooleanArray> attribution = mWakeupAttribution.get(
+ final SparseArray<SparseIntArray> attribution = mWakeupAttribution.get(
wakeup.mElapsedMillis);
if (attribution == null) {
pw.println("N/A");
@@ -292,15 +338,17 @@
int attributed = IntPair.first(counters);
final int total = IntPair.second(counters) + 1;
- pw.print("subsystem: " + subsystemToString(attribution.keyAt(subsystemIdx)));
- pw.print(", uids: [");
- final SparseBooleanArray uids = attribution.valueAt(subsystemIdx);
- if (uids != null) {
- for (int uidIdx = 0; uidIdx < uids.size(); uidIdx++) {
+ pw.print(subsystemToString(attribution.keyAt(subsystemIdx)));
+ pw.print(" [");
+ final SparseIntArray uidProcStates = attribution.valueAt(subsystemIdx);
+ if (uidProcStates != null) {
+ for (int uidIdx = 0; uidIdx < uidProcStates.size(); uidIdx++) {
if (uidIdx > 0) {
pw.print(", ");
}
- UserHandle.formatUid(pw, uids.keyAt(uidIdx));
+ UserHandle.formatUid(pw, uidProcStates.keyAt(uidIdx));
+ pw.print(" " + ActivityManager.procStateToString(
+ uidProcStates.valueAt(uidIdx)));
}
attributed++;
}
@@ -330,29 +378,39 @@
pw.println();
}
+ /**
+ * This class stores recent unattributed activity history per subsystem.
+ * The activity is stored as a mapping of subsystem to timestamp to uid to procstate.
+ */
private static final class WakingActivityHistory {
private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
- private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
+ private SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity =
new SparseArray<>();
- void recordActivity(int subsystem, long elapsedRealtime, int... uids) {
- if (uids == null) {
+ void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) {
+ if (uidProcStates == null) {
return;
}
- TimeSparseArray<SparseBooleanArray> wakingActivity = mWakingActivity.get(subsystem);
+ TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem);
if (wakingActivity == null) {
wakingActivity = new TimeSparseArray<>();
mWakingActivity.put(subsystem, wakingActivity);
}
- SparseBooleanArray uidsToBlame = wakingActivity.get(elapsedRealtime);
+ final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime);
if (uidsToBlame == null) {
- uidsToBlame = new SparseBooleanArray(uids.length);
+ wakingActivity.put(elapsedRealtime, uidProcStates.clone());
+ } else {
+ for (int i = 0; i < uidProcStates.size(); i++) {
+ final int uid = uidProcStates.keyAt(i);
+ // Just in case there are duplicate uids reported with the same timestamp,
+ // keep the processState which was reported first.
+ if (uidsToBlame.indexOfKey(uid) < 0) {
+ uidsToBlame.put(uid, uidProcStates.valueAt(i));
+ }
+ }
wakingActivity.put(elapsedRealtime, uidsToBlame);
}
- for (int i = 0; i < uids.length; i++) {
- uidsToBlame.put(uids[i], true);
- }
// Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS.
// Note that the last activity is always present, even if it occurred before
// WAKING_ACTIVITY_RETENTION_MS.
@@ -365,7 +423,7 @@
void clearAllBefore(long elapsedRealtime) {
for (int subsystemIdx = mWakingActivity.size() - 1; subsystemIdx >= 0; subsystemIdx--) {
- final TimeSparseArray<SparseBooleanArray> activityPerSubsystem =
+ final TimeSparseArray<SparseIntArray> activityPerSubsystem =
mWakingActivity.valueAt(subsystemIdx);
final int endIdx = activityPerSubsystem.closestIndexOnOrBefore(elapsedRealtime);
for (int removeIdx = endIdx; removeIdx >= 0; removeIdx--) {
@@ -377,20 +435,20 @@
}
}
- SparseBooleanArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
- final SparseBooleanArray uidsToReturn = new SparseBooleanArray();
+ SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) {
+ final SparseIntArray uidsToReturn = new SparseIntArray();
- final TimeSparseArray<SparseBooleanArray> activityForSubsystem =
+ final TimeSparseArray<SparseIntArray> activityForSubsystem =
mWakingActivity.get(subsystem);
if (activityForSubsystem != null) {
final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed);
final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed);
for (int i = endIdx; i >= startIdx; i--) {
- final SparseBooleanArray uidsForTime = activityForSubsystem.valueAt(i);
+ final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i);
for (int j = 0; j < uidsForTime.size(); j++) {
- if (uidsForTime.valueAt(j)) {
- uidsToReturn.put(uidsForTime.keyAt(j), true);
- }
+ // In case the same uid appears in different uidsForTime maps, there is no
+ // good way to choose one processState, so just arbitrarily pick any.
+ uidsToReturn.put(uidsForTime.keyAt(j), uidsForTime.valueAt(j));
}
}
// More efficient to remove in a separate loop as it avoids repeatedly calling gc().
@@ -409,25 +467,23 @@
pw.increaseIndent();
for (int i = 0; i < mWakingActivity.size(); i++) {
pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":");
- final LongSparseArray<SparseBooleanArray> wakingActivity =
- mWakingActivity.valueAt(i);
+ final TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i);
if (wakingActivity == null) {
continue;
}
pw.increaseIndent();
for (int j = wakingActivity.size() - 1; j >= 0; j--) {
TimeUtils.formatDuration(wakingActivity.keyAt(j), nowElapsed, pw);
- final SparseBooleanArray uidsToBlame = wakingActivity.valueAt(j);
+ final SparseIntArray uidsToBlame = wakingActivity.valueAt(j);
if (uidsToBlame == null) {
pw.println();
continue;
}
pw.print(": ");
for (int k = 0; k < uidsToBlame.size(); k++) {
- if (uidsToBlame.valueAt(k)) {
- UserHandle.formatUid(pw, uidsToBlame.keyAt(k));
- pw.print(", ");
- }
+ UserHandle.formatUid(pw, uidsToBlame.keyAt(k));
+ pw.print(" [" + ActivityManager.procStateToString(uidsToBlame.valueAt(k)));
+ pw.print("], ");
}
pw.println();
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index fdc2822..8da9e1d 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -154,7 +154,6 @@
import android.security.metrics.KeystoreAtom;
import android.security.metrics.KeystoreAtomPayload;
import android.security.metrics.RkpErrorStats;
-import android.security.metrics.RkpPoolStats;
import android.security.metrics.StorageStats;
import android.stats.storage.StorageEnums;
import android.telephony.ModemActivityInfo;
@@ -738,7 +737,6 @@
return pullInstalledIncrementalPackagesLocked(atomTag, data);
}
case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
- case FrameworkStatsLog.RKP_POOL_STATS:
case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO:
@@ -950,7 +948,6 @@
registerSettingsStats();
registerInstalledIncrementalPackages();
registerKeystoreStorageStats();
- registerRkpPoolStats();
registerKeystoreKeyCreationWithGeneralInfo();
registerKeystoreKeyCreationWithAuthInfo();
registerKeystoreKeyCreationWithPurposeModesInfo();
@@ -4316,14 +4313,6 @@
mStatsCallbackImpl);
}
- private void registerRkpPoolStats() {
- mStatsManager.setPullAtomCallback(
- FrameworkStatsLog.RKP_POOL_STATS,
- null, // use default PullAtomMetadata values,
- DIRECT_EXECUTOR,
- mStatsCallbackImpl);
- }
-
private void registerKeystoreKeyCreationWithGeneralInfo() {
mStatsManager.setPullAtomCallback(
FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO,
@@ -4431,19 +4420,6 @@
return StatsManager.PULL_SUCCESS;
}
- int parseRkpPoolStats(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
- for (KeystoreAtom atomWrapper : atoms) {
- if (atomWrapper.payload.getTag() != KeystoreAtomPayload.rkpPoolStats) {
- return StatsManager.PULL_SKIP;
- }
- RkpPoolStats atom = atomWrapper.payload.getRkpPoolStats();
- pulledData.add(FrameworkStatsLog.buildStatsEvent(
- FrameworkStatsLog.RKP_POOL_STATS, atom.security_level, atom.expiring,
- atom.unassigned, atom.attested, atom.total));
- }
- return StatsManager.PULL_SUCCESS;
- }
-
int parseKeystoreKeyCreationWithGeneralInfo(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
for (KeystoreAtom atomWrapper : atoms) {
if (atomWrapper.payload.getTag()
@@ -4576,8 +4552,6 @@
switch (atomTag) {
case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
return parseKeystoreStorageStats(atoms, pulledData);
- case FrameworkStatsLog.RKP_POOL_STATS:
- return parseRkpPoolStats(atoms, pulledData);
case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
return parseKeystoreKeyCreationWithGeneralInfo(atoms, pulledData);
case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 35e88c1..363d2fd 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -84,6 +84,7 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
@@ -902,12 +903,12 @@
}
@Override
- public void handleSystemKey(int key) throws RemoteException {
+ public void handleSystemKey(KeyEvent key) throws RemoteException {
if (!checkCanCollapseStatusBar("handleSystemKey")) {
return;
}
- mLastSystemKey = key;
+ mLastSystemKey = key.getKeyCode();
if (mBar != null) {
try {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 94e041a..5f56923 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -179,6 +179,9 @@
import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT;
import static com.android.server.wm.ActivityRecordProto.SHOULD_SEND_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
import static com.android.server.wm.ActivityRecordProto.STARTING_MOVED;
@@ -10228,6 +10231,12 @@
proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
+ proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
+ mLetterboxUiController.shouldForceRotateForCameraCompat());
+ proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
+ mLetterboxUiController.shouldRefreshActivityForCameraCompat());
+ proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
+ mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c9213d5..d2fc393 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -100,6 +100,7 @@
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -2011,7 +2012,7 @@
return;
}
- if (r == mRootWindowContainer.getTopResumedActivity()) {
+ if (r.isState(RESUMED) && r == mRootWindowContainer.getTopResumedActivity()) {
setLastResumedActivityUncheckLocked(r, "setFocusedTask-alreadyTop");
return;
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 22dd0e5..d31fe23 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1981,6 +1981,14 @@
return;
}
+ if (controlTarget != null) {
+ final WindowState win = controlTarget.getWindow();
+
+ if (win != null && win.isActivityTypeDream()) {
+ return;
+ }
+ }
+
final @InsetsType int restorePositionTypes = (Type.statusBars() | Type.navigationBars())
& controlTarget.getRequestedVisibleTypes();
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 052c09a..d65f464 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -99,23 +99,6 @@
}
}
- WindowState getHostWindow(IBinder inputToken) {
- EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
- return embeddedWindow != null ? embeddedWindow.mHostWindowState : null;
- }
-
- boolean isOverlay(IBinder inputToken) {
- EmbeddedWindow embeddedWindow = mWindows.get(inputToken);
- return embeddedWindow != null ? embeddedWindow.getIsOverlay() : false;
- }
-
- void setIsOverlay(IBinder focusGrantToken) {
- EmbeddedWindow embeddedWindow = mWindowsByFocusToken.get(focusGrantToken);
- if (embeddedWindow != null) {
- embeddedWindow.setIsOverlay();
- }
- }
-
void remove(IWindow client) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
EmbeddedWindow ew = mWindows.valueAt(i);
@@ -176,14 +159,15 @@
public Session mSession;
InputChannel mInputChannel;
final int mWindowType;
- // Track whether the EmbeddedWindow is a system hosted overlay via
- // {@link OverlayHost}. In the case of client hosted overlays, the client
- // view hierarchy will take care of invoking requestEmbeddedWindowFocus
- // but for system hosted overlays we have to do this via tapOutsideDetection
- // and this variable is mostly used for tracking that.
- boolean mIsOverlay = false;
- private IBinder mFocusGrantToken;
+ /**
+ * A unique token associated with the embedded window that can be used by the host window
+ * to request focus transfer to the embedded. This is not the input token since we don't
+ * want to give clients access to each others input token.
+ */
+ private final IBinder mFocusGrantToken;
+
+ private boolean mIsFocusable;
/**
* @param session calling session to check ownership of the window
@@ -199,7 +183,8 @@
*/
EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
- int displayId, IBinder focusGrantToken, String inputHandleName) {
+ int displayId, IBinder focusGrantToken, String inputHandleName,
+ boolean isFocusable) {
mSession = session;
mWmService = service;
mClient = clientToken;
@@ -214,6 +199,7 @@
final String hostWindowName =
(mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
: "";
+ mIsFocusable = isFocusable;
mName = "Embedded{" + inputHandleName + hostWindowName + "}";
}
@@ -279,13 +265,6 @@
return mOwnerUid;
}
- void setIsOverlay() {
- mIsOverlay = true;
- }
- boolean getIsOverlay() {
- return mIsOverlay;
- }
-
IBinder getFocusGrantToken() {
return mFocusGrantToken;
}
@@ -297,20 +276,33 @@
return null;
}
+ void setIsFocusable(boolean isFocusable) {
+ mIsFocusable = isFocusable;
+ }
+
/**
- * System hosted overlays need the WM to invoke grantEmbeddedWindowFocus and
- * so we need to participate inside handlePointerDownOutsideFocus logic
- * however client hosted overlays will rely on the hosting view hierarchy
- * to grant and revoke focus, and so the server side logic is not needed.
+ * When an embedded window is touched when it's not currently focus, we need to switch
+ * focus to that embedded window unless the embedded window was marked as not focusable.
*/
@Override
public boolean receiveFocusFromTapOutside() {
- return mIsOverlay;
+ return mIsFocusable;
}
private void handleTap(boolean grantFocus) {
if (mInputChannel != null) {
- mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
+ if (mHostWindowState != null) {
+ mWmService.grantEmbeddedWindowFocus(mSession, mHostWindowState.mClient,
+ mFocusGrantToken, grantFocus);
+ if (grantFocus) {
+ // If granting focus to the embedded when tapped, we need to ensure the host
+ // gains focus as well or the transfer won't take effect since it requires
+ // the host to transfer the focus to the embedded.
+ mHostWindowState.handleTapOutsideFocusInsideSelf();
+ }
+ } else {
+ mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 4be98a3..b4dffdc 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -48,7 +48,7 @@
* Controller for IME inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
*/
-final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider {
+final class ImeInsetsSourceProvider extends InsetsSourceProvider {
/** The token tracking the current IME request or {@code null} otherwise. */
@Nullable
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 301c184..3d4e0eb 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -289,6 +289,14 @@
mChanged = true;
}
+ void setFocusTransferTarget(IBinder toToken) {
+ if (mHandle.focusTransferTarget == toToken) {
+ return;
+ }
+ mHandle.focusTransferTarget = toToken;
+ mChanged = true;
+ }
+
@Override
public String toString() {
return mHandle + ", changed=" + mChanged;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a8c9cd3..fe13b87 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -223,10 +223,10 @@
startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ final SparseArray<InsetsSourceProvider> providers =
mStateController.getSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider provider = providers.valueAt(i);
+ final InsetsSourceProvider provider = providers.valueAt(i);
if (!isTransient(provider.getSource().getType())) {
continue;
}
@@ -341,11 +341,10 @@
}
}
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
- mStateController.getSourceProviders();
+ final SparseArray<InsetsSourceProvider> providers = mStateController.getSourceProviders();
final int windowType = attrs.type;
for (int i = providers.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+ final InsetsSourceProvider otherProvider = providers.valueAt(i);
if (otherProvider.overridesFrame(windowType)) {
if (state == originalState) {
state = new InsetsState(state);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 0953604..3b23f97 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -58,7 +58,7 @@
* Controller for a specific inset source on the server. It's called provider as it provides the
* {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
*/
-abstract class InsetsSourceProvider {
+class InsetsSourceProvider {
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index fca333d..249ead0 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -56,7 +56,7 @@
private final InsetsState mState = new InsetsState();
private final DisplayContent mDisplayContent;
- private final SparseArray<WindowContainerInsetsSourceProvider> mProviders = new SparseArray<>();
+ private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
mControlTargetProvidersMap = new ArrayMap<>();
private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
@@ -106,22 +106,22 @@
return result;
}
- SparseArray<WindowContainerInsetsSourceProvider> getSourceProviders() {
+ SparseArray<InsetsSourceProvider> getSourceProviders() {
return mProviders;
}
/**
* @return The provider of a specific source ID.
*/
- WindowContainerInsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
- WindowContainerInsetsSourceProvider provider = mProviders.get(id);
+ InsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
+ InsetsSourceProvider provider = mProviders.get(id);
if (provider != null) {
return provider;
}
final InsetsSource source = mState.getOrCreateSource(id, type);
provider = id == ID_IME
? new ImeInsetsSourceProvider(source, this, mDisplayContent)
- : new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
+ : new InsetsSourceProvider(source, this, mDisplayContent);
mProviders.put(id, provider);
return provider;
}
@@ -334,7 +334,7 @@
}
mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
for (int i = mProviders.size() - 1; i >= 0; i--) {
- final WindowContainerInsetsSourceProvider provider = mProviders.valueAt(i);
+ final InsetsSourceProvider provider = mProviders.valueAt(i);
provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b7e2265..db44532 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4687,6 +4687,7 @@
if (!isAttached()) {
return;
}
+ mTransitionController.collect(this);
final TaskDisplayArea taskDisplayArea = getDisplayArea();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c6ba600..0fe1f92 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -50,6 +50,7 @@
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
@@ -183,6 +184,12 @@
private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
/**
+ * The (non alwaysOnTop) tasks which were on-top of their display before the transition. If
+ * tasks are nested, all the tasks that are parents of the on-top task are also included.
+ */
+ private final ArrayList<Task> mOnTopTasksStart = new ArrayList<>();
+
+ /**
* Set of participating windowtokens (activity/wallpaper) which are visible at the end of
* the transition animation.
*/
@@ -515,6 +522,7 @@
mParticipants.add(wc);
if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
mTargetDisplays.add(wc.getDisplayContent());
+ addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart);
}
if (info.mShowWallpaper) {
// Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
@@ -526,6 +534,27 @@
}
}
+ /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */
+ private static void addOnTopTasks(Task task, ArrayList<Task> out) {
+ for (int i = task.getChildCount() - 1; i >= 0; --i) {
+ final Task child = task.getChildAt(i).asTask();
+ if (child == null) return;
+ if (child.getWindowConfiguration().isAlwaysOnTop()) continue;
+ out.add(child);
+ addOnTopTasks(child, out);
+ break;
+ }
+ }
+
+ /** Get the top non-alwaysOnTop leaf task on the display `dc`. */
+ private static void addOnTopTasks(DisplayContent dc, ArrayList<Task> out) {
+ final Task topNotAlwaysOnTop = dc.getRootTask(
+ t -> !t.getWindowConfiguration().isAlwaysOnTop());
+ if (topNotAlwaysOnTop == null) return;
+ out.add(topNotAlwaysOnTop);
+ addOnTopTasks(topNotAlwaysOnTop, out);
+ }
+
/**
* Records wc as changing its state of existence during this transition. For example, a new
* task is considered an existence change while moving a task to front is not. wc is added
@@ -1000,11 +1029,13 @@
InsetsControlTarget prevImeTarget = dc.getImeTarget(
DisplayContent.IME_TARGET_CONTROL);
InsetsControlTarget newImeTarget = null;
+ TaskDisplayArea transientTDA = null;
// Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
// so re-compute in case the IME target is changed after transition.
for (int t = 0; t < mTransientLaunches.size(); ++t) {
if (mTransientLaunches.keyAt(t).getDisplayContent() == dc) {
newImeTarget = dc.computeImeTarget(true /* updateImeTarget */);
+ transientTDA = mTransientLaunches.keyAt(i).getTaskDisplayArea();
break;
}
}
@@ -1015,10 +1046,17 @@
InputMethodManagerInternal.get().updateImeWindowStatus(
false /* disableImeIcon */);
}
+ // An uncommitted transient launch can leave incomplete lifecycles if visibilities
+ // didn't change (eg. re-ordering with translucent tasks will leave launcher
+ // in RESUMED state), so force an update here.
+ if (!hasVisibleTransientLaunch && transientTDA != null) {
+ transientTDA.pauseBackTasks(null /* resuming */);
+ }
}
dc.removeImeSurfaceImmediately();
dc.handleCompleteDeferredRemoval();
}
+ validateKeyguardOcclusion();
validateVisibility();
mState = STATE_FINISHED;
@@ -1140,9 +1178,13 @@
}
// Check whether the participants were animated from back navigation.
mController.mAtm.mBackNavigationController.onTransactionReady(this);
+
+ collectOrderChanges();
+
// Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
+ info.setDebugId(mSyncId);
// Repopulate the displays based on the resolved targets.
mTargetDisplays.clear();
@@ -1175,8 +1217,6 @@
if (mRecentsDisplayId != INVALID_DISPLAY) break;
}
- handleNonAppWindowsInTransition(mType, mFlags);
-
// The callback is only populated for custom activity-level client animations
sendRemoteCallback(mClientAnimationStartCallback);
@@ -1291,6 +1331,27 @@
info.releaseAnimSurfaces();
}
+ /** Collect tasks which moved-to-top but didn't change otherwise. */
+ @VisibleForTesting
+ void collectOrderChanges() {
+ if (mOnTopTasksStart.isEmpty()) return;
+ final ArrayList<Task> onTopTasksEnd = new ArrayList<>();
+ for (int i = 0; i < mTargetDisplays.size(); ++i) {
+ addOnTopTasks(mTargetDisplays.get(i), onTopTasksEnd);
+ }
+ for (int i = 0; i < onTopTasksEnd.size(); ++i) {
+ final Task task = onTopTasksEnd.get(i);
+ if (mOnTopTasksStart.contains(task)) continue;
+ mParticipants.add(task);
+ int changeIdx = mChanges.indexOfKey(task);
+ if (changeIdx < 0) {
+ mChanges.put(task, new ChangeInfo(task));
+ changeIdx = mChanges.indexOfKey(task);
+ }
+ mChanges.valueAt(changeIdx).mFlags |= ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP;
+ }
+ }
+
private void postCleanupOnFailure() {
mController.mAtm.mH.post(() -> {
synchronized (mController.mAtm.mGlobalLock) {
@@ -1480,19 +1541,6 @@
}
}
- private void handleNonAppWindowsInTransition(
- @TransitionType int transit, @TransitionFlags int flags) {
- if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
- // If the occlusion changed but the transition isn't an occlude/unocclude transition,
- // then we have to notify KeyguardService directly. This can happen if there is
- // another ongoing transition when the app changes occlusion OR if the app dies or
- // is killed. Both of these are common during tests.
- if (transit != TRANSIT_KEYGUARD_OCCLUDE && transit != TRANSIT_KEYGUARD_UNOCCLUDE) {
- mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
- }
- }
- }
-
private void reportStartReasonsToLogger() {
// Record transition start in metrics logger. We just assume everything is "DRAWN"
// at this point since splash-screen is a presentation (shell) detail.
@@ -2185,6 +2233,13 @@
return mainWin.getAttrs().rotationAnimation;
}
+ private void validateKeyguardOcclusion() {
+ if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
+ mController.mStateValidators.add(
+ mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+ }
+ }
+
private void validateVisibility() {
for (int i = mTargets.size() - 1; i >= 0; --i) {
if (reduceMode(mTargets.get(i).mReadyMode) != TRANSIT_CLOSE) {
@@ -2246,13 +2301,17 @@
*/
private static final int FLAG_CHANGE_YES_ANIMATION = 0x10;
+ /** Whether this change's container moved to the top. */
+ private static final int FLAG_CHANGE_MOVED_TO_TOP = 0x20;
+
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
FLAG_SEAMLESS_ROTATION,
FLAG_TRANSIENT_LAUNCH,
FLAG_ABOVE_TRANSIENT_LAUNCH,
FLAG_CHANGE_NO_ANIMATION,
- FLAG_CHANGE_YES_ANIMATION
+ FLAG_CHANGE_YES_ANIMATION,
+ FLAG_CHANGE_MOVED_TO_TOP
})
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
@@ -2283,7 +2342,7 @@
int mDisplayId = -1;
@ActivityInfo.Config int mKnownConfigChanges;
- /** These are just extra info. They aren't used for change-detection. */
+ /** Extra information about this change. */
@Flag int mFlags = FLAG_NONE;
/** Snapshot surface and luma, if relevant. */
@@ -2335,7 +2394,8 @@
|| (mWindowingMode != 0 && mContainer.getWindowingMode() != mWindowingMode)
|| !mContainer.getBounds().equals(mAbsoluteBounds)
|| mRotation != mContainer.getWindowConfiguration().getRotation()
- || mDisplayId != getDisplayId(mContainer);
+ || mDisplayId != getDisplayId(mContainer)
+ || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
}
@TransitionInfo.TransitionMode
@@ -2436,6 +2496,9 @@
&& (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
flags |= FLAG_NO_ANIMATION;
}
+ if ((mFlags & FLAG_CHANGE_MOVED_TO_TOP) != 0) {
+ flags |= FLAG_MOVED_TO_TOP;
+ }
return flags;
}
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index 57c0d65..a4c931c 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -365,6 +365,24 @@
Trace.endSection();
}
+ /**
+ * Being called while taking a bugreport so that tracing files can be included in the bugreport.
+ *
+ * @param pw Print writer
+ */
+ public void saveForBugreport(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+ return;
+ }
+ Trace.beginSection("TransitionTracer#saveForBugreport");
+ synchronized (mEnabledLock) {
+ final File outputFile = new File(TRACE_FILE);
+ writeTraceToFileLocked(pw, outputFile);
+ }
+ Trace.endSection();
+ }
+
boolean isActiveTracingEnabled() {
return mActiveTracingEnabled;
}
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index 88c410b..f8edc2b 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -90,8 +90,6 @@
requireOverlaySurfaceControl();
mOverlays.add(p);
- mWmService.mEmbeddedWindowController.setIsOverlay(p.getInputToken());
-
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
t.reparent(p.getSurfaceControl(), mSurfaceControl)
.show(p.getSurfaceControl());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2e21782..4117641 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4122,7 +4122,7 @@
}
private void hideInsetSourceViewOverflows() {
- final SparseArray<WindowContainerInsetsSourceProvider> providers =
+ final SparseArray<InsetsSourceProvider> providers =
getDisplayContent().getInsetsStateController().getSourceProviders();
for (int i = providers.size(); i >= 0; i--) {
final InsetsSourceProvider insetProvider = providers.valueAt(i);
diff --git a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java b/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
deleted file mode 100644
index aa2e8f5..0000000
--- a/services/core/java/com/android/server/wm/WindowContainerInsetsSourceProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.view.InsetsSource;
-
-/**
- * Controller for a specific inset source on the server. It's called provider as it provides the
- * {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
- */
-class WindowContainerInsetsSourceProvider extends InsetsSourceProvider {
- // TODO(b/218734524): Move the window container specific stuff from InsetsSourceProvider to
- // this class.
-
- WindowContainerInsetsSourceProvider(InsetsSource source,
- InsetsStateController stateController, DisplayContent displayContent) {
- super(source, stateController, displayContent);
- }
-}
-
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d7c9d9c..2156c6d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8600,7 +8600,8 @@
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, window,
mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
- sanitizedType, displayId, focusGrantToken, inputHandleName);
+ sanitizedType, displayId, focusGrantToken, inputHandleName,
+ (flags & FLAG_NOT_FOCUSABLE) == 0);
clientChannel = win.openInputChannel();
mEmbeddedWindowController.add(clientChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
@@ -8721,6 +8722,7 @@
}
name = win.toString();
applicationHandle = win.getApplicationHandle();
+ win.setIsFocusable((flags & FLAG_NOT_FOCUSABLE) == 0);
}
updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name,
@@ -8998,24 +9000,23 @@
Slog.e(TAG, "Embedded window does not belong to the host");
return;
}
- SurfaceControl.Transaction t = mTransactionFactory.get();
if (grantFocus) {
- t.requestFocusTransfer(embeddedWindow.getInputChannelToken(), embeddedWindow.toString(),
- hostWindow.mInputChannel.getToken(),
- hostWindow.getName(),
- hostWindow.getDisplayId()).apply();
+ hostWindow.mInputWindowHandle.setFocusTransferTarget(
+ embeddedWindow.getInputChannelToken());
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
"Transfer focus request " + embeddedWindow,
"reason=grantEmbeddedWindowFocus(true)");
} else {
- t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(),
- embeddedWindow.getInputChannelToken(),
- embeddedWindow.toString(),
- hostWindow.getDisplayId()).apply();
+ hostWindow.mInputWindowHandle.setFocusTransferTarget(null);
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
"Transfer focus request " + hostWindow,
"reason=grantEmbeddedWindowFocus(false)");
}
+ DisplayContent dc = mRoot.getDisplayContent(hostWindow.getDisplayId());
+ if (dc != null) {
+ dc.getInputMonitor().updateInputWindowsLw(true);
+ }
+
ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
embeddedWindow, grantFocus);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 437af4b..a153708 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1301,6 +1301,9 @@
case "stop":
mInternal.mTransitionTracer.stopTrace(pw);
break;
+ case "save-for-bugreport":
+ mInternal.mTransitionTracer.saveForBugreport(pw);
+ break;
default:
getErrPrintWriter()
.println("Error: expected 'start' or 'stop', but got '" + arg + "'");
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 71acbb4..d64b5a1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -136,7 +136,6 @@
jmethodID getContextForDisplay;
jmethodID notifyDropWindow;
jmethodID getParentSurfaceForPointers;
- jmethodID isPerDisplayTouchModeEnabled;
} gServiceClassInfo;
static struct {
@@ -369,10 +368,6 @@
virtual PointerIconStyle getCustomPointerIconId();
virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position);
- /* --- If touch mode is enabled per display or global --- */
-
- virtual bool isPerDisplayTouchModeEnabled();
-
private:
sp<InputManagerInterface> mInputManager;
@@ -1645,16 +1640,6 @@
InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
}
-bool NativeInputManager::isPerDisplayTouchModeEnabled() {
- JNIEnv* env = jniEnv();
- jboolean enabled =
- env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isPerDisplayTouchModeEnabled);
- if (checkAndClearExceptionFromCallback(env, "isPerDisplayTouchModeEnabled")) {
- return false;
- }
- return static_cast<bool>(enabled);
-}
-
FloatPoint NativeInputManager::getMouseCursorPosition() {
std::scoped_lock _l(mLock);
const auto pc = mLocked.pointerController.lock();
@@ -2846,9 +2831,6 @@
GET_METHOD_ID(gServiceClassInfo.getParentSurfaceForPointers, clazz,
"getParentSurfaceForPointers", "(I)J");
- GET_METHOD_ID(gServiceClassInfo.isPerDisplayTouchModeEnabled, clazz,
- "isPerDisplayTouchModeEnabled", "()Z");
-
// InputDevice
FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp
index c7a1af7..b21489a 100644
--- a/services/core/jni/gnss/AGnssRil.cpp
+++ b/services/core/jni/gnss/AGnssRil.cpp
@@ -89,6 +89,10 @@
}
jboolean AGnssRil::injectNiSuplMessageData(const jbyteArray& msgData, jint length, jint slotIndex) {
+ if (mIAGnssRil->getInterfaceVersion() <= 2) {
+ ALOGE("IAGnssRil does not support injectNiSuplMessageData().");
+ return JNI_FALSE;
+ }
JNIEnv* env = getJniEnv();
jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(msgData, 0));
auto status = mIAGnssRil->injectNiSuplMessageData(std::vector<uint8_t>((const uint8_t*)bytes,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f01b0eb..7d26c04 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9193,34 +9193,53 @@
Objects.requireNonNull(who, "ComponentName is null");
}
-
final int userHandle = caller.getUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
// SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
- ap = enforcePermissionAndGetEnforcingAdmin(
+ EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
- affectedUserId).getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
- }
- if (isManagedProfile(userHandle)) {
- if (parent) {
- if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
- which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
- } else {
- which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
- }
+ affectedUserId);
+ if (which == 0) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.KEYGUARD_DISABLED_FEATURES, admin, affectedUserId);
} else {
- which = which & PROFILE_KEYGUARD_FEATURES;
+ // TODO(b/273723433): revisit silent masking of features
+ if (isManagedProfile(userHandle)) {
+ if (parent) {
+ if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ } else {
+ which = which
+ & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ }
+ } else {
+ which = which & PROFILE_KEYGUARD_FEATURES;
+ }
+ }
+ mDevicePolicyEngine.setLocalPolicy(PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+ admin, new IntegerPolicyValue(which), affectedUserId);
}
- }
- if (ap.disabledKeyguardFeatures != which) {
- ap.disabledKeyguardFeatures = which;
- saveSettingsLocked(userHandle);
+ invalidateBinderCaches();
+ } else {
+ ActiveAdmin ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
+ if (isManagedProfile(userHandle)) {
+ if (parent) {
+ if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ } else {
+ which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ }
+ } else {
+ which = which & PROFILE_KEYGUARD_FEATURES;
+ }
+ }
+ if (ap.disabledKeyguardFeatures != which) {
+ ap.disabledKeyguardFeatures = which;
+ saveSettingsLocked(userHandle);
+ }
}
}
if (SecurityLog.isLoggingEnabled()) {
@@ -9252,15 +9271,51 @@
Preconditions.checkCallAuthorization(
who == null || isCallingFromPackage(who.getPackageName(), caller.getUid())
|| isSystemUid(caller));
+ int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- synchronized (getLockObject()) {
- if (who != null) {
+ synchronized (getLockObject()) {
+ if (who != null) {
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ EnforcingAdmin admin = getEnforcingAdminForCaller(
+ who, who.getPackageName());
+ Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+ PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+ admin,
+ affectedUserId);
+ return features == null ? 0 : features;
+ } else {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
return (admin != null) ? admin.disabledKeyguardFeatures : 0;
}
+ }
+ if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
+ Integer features = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+ affectedUserId);
+
+ return Binder.withCleanCallingIdentity(() -> {
+ int combinedFeatures = features == null ? 0 : features;
+ List<UserInfo> profiles = mUserManager.getProfiles(affectedUserId);
+ for (UserInfo profile : profiles) {
+ int profileId = profile.id;
+ if (profileId == affectedUserId) {
+ continue;
+ }
+ Integer profileFeatures = mDevicePolicyEngine.getResolvedPolicy(
+ PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+ profileId);
+ if (profileFeatures != null) {
+ combinedFeatures |= (profileFeatures
+ & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER);
+ }
+ }
+ return combinedFeatures;
+ });
+ }
+
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
final List<ActiveAdmin> admins;
if (!parent && isManagedProfile(userHandle)) {
// If we are being asked about a managed profile, just return keyguard features
@@ -9290,9 +9345,9 @@
}
}
return which;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
}
}
@@ -12519,9 +12574,8 @@
case UserManager.RESTRICTION_NOT_SET:
return false;
case UserManager.RESTRICTION_SOURCE_DEVICE_OWNER:
- return !isDeviceOwner(admin, userId);
case UserManager.RESTRICTION_SOURCE_PROFILE_OWNER:
- return !isProfileOwner(admin, userId);
+ return !(isDeviceOwner(admin, userId) || isProfileOwner(admin, userId));
default:
return true;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a08c2054..8812c3d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -246,6 +246,14 @@
(Long value, Context context, Integer userId, PolicyKey policyKey) -> true,
new LongPolicySerializer());
+ static PolicyDefinition<Integer> KEYGUARD_DISABLED_FEATURES = new PolicyDefinition<>(
+ new NoArgsPolicyKey(DevicePolicyIdentifiers.KEYGUARD_DISABLED_FEATURES_POLICY),
+ new FlagUnion(),
+ POLICY_FLAG_LOCAL_ONLY_POLICY,
+ // Nothing is enforced for keyguard features, we just need to store it
+ (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ new IntegerPolicySerializer());
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c7f5223..dbff90a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,6 +108,8 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.ambientcontext.AmbientContextManagerService;
import com.android.server.appbinding.AppBindingService;
+import com.android.server.appop.AppOpMigrationHelper;
+import com.android.server.appop.AppOpMigrationHelperImpl;
import com.android.server.art.ArtModuleServiceInitializer;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.attention.AttentionManagerService;
@@ -1136,6 +1138,8 @@
t.traceBegin("StartAccessCheckingService");
LocalServices.addService(PermissionMigrationHelper.class,
new PermissionMigrationHelperImpl());
+ LocalServices.addService(AppOpMigrationHelper.class,
+ new AppOpMigrationHelperImpl());
mSystemServiceManager.startService(AccessCheckingService.class);
t.traceEnd();
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
new file mode 100644
index 0000000..71e4f2a
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.permission.access.appop
+
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+
+class AppIdAppOpMigration {
+ fun migrateUserState(state: AccessState, userId: Int) {
+ val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
+ val legacyAppIdAppOpModes = legacyAppOpsManager.getLegacyAppIdAppOpModes(userId)
+ val appIdAppOpModes = state.userStates[userId].appIdAppOpModes
+ val version = PackageVersionMigration.getVersion(userId)
+ legacyAppIdAppOpModes.forEach { (appId, legacyAppOpModes) ->
+ val appOpModes = appIdAppOpModes.getOrPut(appId) { IndexedMap() }
+ legacyAppOpModes.forEach { (appOpName, appOpMode) ->
+ appOpModes[appOpName] = appOpMode
+ }
+
+ state.systemState.appIds[appId].forEachIndexed { _, packageName ->
+ state.userStates[userId].packageVersions[packageName] = version
+ }
+ }
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 5a2522e..bd94955 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,14 +17,20 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.pm.pkg.PackageState
class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) {
+ private val migration = AppIdAppOpMigration()
+
+ private val upgrade = AppIdAppOpUpgrade(this)
+
@Volatile
private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
private val onAppOpModeChangedListenersLock = Any()
@@ -117,6 +123,18 @@
}
}
+ override fun migrateUserState(state: AccessState, userId: Int) {
+ with(migration) { migrateUserState(state, userId) }
+ }
+
+ override fun MutateStateScope.upgradePackageState(
+ packageState: PackageState,
+ userId: Int,
+ version: Int,
+ ) {
+ with(upgrade) { upgradePackageState(packageState, userId, version) }
+ }
+
/**
* Listener for app op mode changes.
*/
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
new file mode 100644
index 0000000..4bd36f4
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.pm.pkg.PackageState
+
+class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) {
+ fun MutateStateScope.upgradePackageState(
+ packageState: PackageState,
+ userId: Int,
+ version: Int,
+ ) {
+ if (version == 0) {
+ return
+ }
+
+ if (version <= 2) {
+ with(policy) {
+ val appOpMode = getAppOpMode(
+ packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+ )
+ setAppOpMode(
+ packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode
+ )
+ }
+ }
+ if (version <= 13) {
+ val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
+ if (permissionName in packageState.androidPackage!!.requestedPermissions) {
+ with(policy) {
+ val appOpMode = getAppOpMode(
+ packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM
+ )
+ val defaultAppOpMode =
+ AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)
+ if (appOpMode == defaultAppOpMode) {
+ setAppOpMode(
+ packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+ AppOpsManager.MODE_ALLOWED
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
new file mode 100644
index 0000000..c9651b2
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.permission.access.appop
+
+import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.PackageVersionMigration
+
+class PackageAppOpMigration {
+ fun migrateUserState(state: AccessState, userId: Int) {
+ val legacyAppOpsManager = LocalServices.getService(AppOpMigrationHelper::class.java)!!
+ val legacyPackageAppOpModes = legacyAppOpsManager.getLegacyPackageAppOpModes(userId)
+ val packageAppOpModes = state.userStates[userId].packageAppOpModes
+ val version = PackageVersionMigration.getVersion(userId)
+ legacyPackageAppOpModes.forEach { (packageName, legacyAppOpModes) ->
+ val appOpModes = packageAppOpModes.getOrPut(packageName) { IndexedMap() }
+ legacyAppOpModes.forEach { (appOpName, appOpMode) ->
+ appOpModes[appOpName] = appOpMode
+ }
+ state.userStates[userId].packageVersions[packageName] = version
+ }
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 7d3578d..b4c4796 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,14 +17,20 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PackageUri
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.pm.pkg.PackageState
class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+ private val migration = PackageAppOpMigration()
+
+ private val upgrade = PackageAppOpUpgrade(this)
+
@Volatile
private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
private val onAppOpModeChangedListenersLock = Any()
@@ -117,6 +123,18 @@
}
}
+ override fun migrateUserState(state: AccessState, userId: Int) {
+ with(migration) { migrateUserState(state, userId) }
+ }
+
+ override fun MutateStateScope.upgradePackageState(
+ packageState: PackageState,
+ userId: Int,
+ version: Int,
+ ) {
+ with(upgrade) { upgradePackageState(packageState, userId, version) }
+ }
+
/**
* Listener for app op mode changes.
*/
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
new file mode 100644
index 0000000..fdf2b64
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.appop
+
+import android.app.AppOpsManager
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.pm.pkg.PackageState
+
+class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) {
+ fun MutateStateScope.upgradePackageState(
+ packageState: PackageState,
+ userId: Int,
+ version: Int,
+ ) {
+ if (version == 0) {
+ return
+ }
+
+ if (version <= 2) {
+ with(policy) {
+ val appOpMode = getAppOpMode(
+ packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND
+ )
+ setAppOpMode(
+ packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND,
+ appOpMode
+ )
+ }
+ }
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
index ee7a4f4..b4b185f 100644
--- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
+++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt
@@ -16,7 +16,9 @@
package com.android.server.permission.access.util
+import android.util.Log
import com.android.server.LocalServices
+import com.android.server.appop.AppOpMigrationHelper
import com.android.server.permission.access.AccessPolicy
import com.android.server.pm.permission.PermissionMigrationHelper
@@ -31,8 +33,8 @@
LocalServices.getService(PermissionMigrationHelper::class.java)
val permissionVersion = permissionMigrationHelper.getLegacyPermissionsVersion(userId)
- // TODO appops version would be fixed in appops cl
- val appOpVersion = 1
+ val appOpMigrationHelper = LocalServices.getService(AppOpMigrationHelper::class.java)
+ val appOpVersion = appOpMigrationHelper.legacyAppOpVersion
return when {
// Both files don't exist.
@@ -57,10 +59,13 @@
permissionVersion == 9 && appOpVersion == 1 -> 12
permissionVersion == 10 && appOpVersion == 1 -> 13
permissionVersion == 10 && appOpVersion == 3 -> AccessPolicy.VERSION_LATEST
- else -> throw IllegalArgumentException(
- "Version combination not recognized, permission" +
- "version: $permissionVersion, app-op version: $appOpVersion"
- )
+ else -> {
+ Log.w(
+ "PackageVersionMigration", "Version combination not recognized, permission" +
+ "version: $permissionVersion, app-op version: $appOpVersion"
+ )
+ AccessPolicy.VERSION_LATEST
+ }
}
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
deleted file mode 100644
index 111cabd..0000000
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/SwitchKeyboardLayoutTest.java
+++ /dev/null
@@ -1,39 +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.server.inputmethod;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class SwitchKeyboardLayoutTest extends InputMethodManagerServiceTestBase {
- @Test
- public void testSwitchToNextKeyboardLayout() {
- ExtendedMockito.spyOn(mInputMethodManagerService.mSwitchingController);
- InputMethodManagerInternal.get().switchKeyboardLayout(1);
- verify(mInputMethodManagerService.mSwitchingController)
- .getNextInputMethodLocked(eq(true) /* onlyCurrentIme */, any(), any());
- }
-}
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
index d813962..a849b66 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
@@ -22,6 +22,7 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@@ -41,6 +42,7 @@
@RunWith(DeviceJUnit4Parameterized::class)
@Parameterized.UseParametersRunnerFactory(
DeviceJUnit4ClassRunnerWithParameters.RunnerFactory::class)
+@Ignore("b/275403538")
class SdCardEjectionTests : BaseHostJUnit4Test() {
companion object {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 36d191b..41a5ddb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -20,6 +20,7 @@
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ALARM;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_FOREGROUND;
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_INTERACTIVE;
@@ -1141,7 +1142,7 @@
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
}
- mImpl.waitForIdle(null);
+ mImpl.waitForIdle(LOG_WRITER_INFO);
// Verify that there is only one delivery event reported since one of the broadcasts
// should have been skipped.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index bca39ae..3b964bc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -18,6 +18,7 @@
import static android.os.UserHandle.USER_SYSTEM;
+import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -222,7 +223,7 @@
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
- realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+ realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
realAms.mPackageManagerInt = mPackageManagerInt;
realAms.mUsageStatsService = mUsageStatsManagerInt;
realAms.mProcessesReady = true;
@@ -659,7 +660,7 @@
}
private void waitForIdle() throws Exception {
- mQueue.waitForIdle(null);
+ mQueue.waitForIdle(LOG_WRITER_INFO);
}
private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception {
@@ -773,9 +774,6 @@
mQueue.dumpToDropBoxLocked(TAG);
BroadcastQueue.logv(TAG);
- BroadcastQueue.logv(TAG, null);
- BroadcastQueue.logv(TAG, new PrintWriter(new ByteArrayOutputStream()));
-
BroadcastQueue.logw(TAG);
assertNotNull(mQueue.toString());
@@ -951,7 +949,7 @@
// cold-started apps to be thawed, but the modern stack does
} else {
// Confirm that app was thawed
- verify(mAms.mOomAdjuster.mCachedAppOptimizer, atLeastOnce()).unfreezeTemporarily(
+ verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
@@ -1394,7 +1392,7 @@
anyInt(), any());
// Finally, verify that we thawed the final receiver
- verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(callerApp),
+ verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
}
@@ -1891,10 +1889,10 @@
assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
}
- mQueue.waitForBarrier(null);
+ mQueue.waitForBarrier(LOG_WRITER_INFO);
assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
- mQueue.waitForIdle(null);
+ mQueue.waitForIdle(LOG_WRITER_INFO);
assertTrue(mQueue.isIdleLocked());
assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 1fbb8dd..eb6efd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -166,10 +166,6 @@
CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo(
CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
@@ -261,10 +257,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo(
@@ -275,10 +267,6 @@
CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo(
CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
@@ -425,10 +413,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 1);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMaxOomAdj).isEqualTo(
@@ -454,10 +438,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
// Repeat for each of the throttle keys.
mCountDown = new CountDownLatch(1);
@@ -472,10 +452,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
mCountDown = new CountDownLatch(1);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -489,10 +465,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
mCountDown = new CountDownLatch(1);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -506,10 +478,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
mCountDown = new CountDownLatch(1);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -523,10 +491,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
mCountDown = new CountDownLatch(1);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -540,10 +504,6 @@
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
- CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
}
@Test
@@ -953,15 +913,7 @@
mProcessDependencies.setRssAfterCompaction(rssAfter);
// When moving within cached state
- mCachedAppOptimizerUnderTest.onOomAdjustChanged(
- ProcessList.CACHED_APP_MIN_ADJ, ProcessList.CACHED_APP_MIN_ADJ + 1, processRecord);
- waitForHandler();
- // THEN process IS NOT compacted.
- assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
-
- // When moving into cached state
- mCachedAppOptimizerUnderTest.onOomAdjustChanged(ProcessList.CACHED_APP_MIN_ADJ - 1,
- ProcessList.CACHED_APP_MIN_ADJ + 1, processRecord);
+ mCachedAppOptimizerUnderTest.onProcessFrozen(processRecord);
waitForHandler();
// THEN process IS compacted.
assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
index b5e0e07..dd44a79 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
@@ -50,6 +50,7 @@
public class ProxyAccessibilityServiceConnectionTest {
private static final int DISPLAY_ID = 1000;
+ private static final int DEVICE_ID = 2000;
private static final int CONNECTION_ID = 1000;
private static final ComponentName COMPONENT_NAME = new ComponentName(
"com.android.server.accessibility", ".ProxyAccessibilityServiceConnectionTest");
@@ -90,7 +91,7 @@
mAccessibilityServiceInfo, CONNECTION_ID , new Handler(
getInstrumentation().getContext().getMainLooper()),
mMockLock, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
- mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID);
+ mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID, DEVICE_ID);
}
@Test
@@ -101,7 +102,7 @@
mProxyConnection.setInstalledAndEnabledServices(infos);
- verify(mMockSystemSupport).onClientChangeLocked(true);
+ verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
new file mode 100644
index 0000000..6eedeea
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class HardwareKeyboardShortcutControllerTest {
+
+ @Test
+ public void testForwardRotation() {
+ final List<String> handles = Arrays.asList("0", "1", "2", "3");
+ assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", true));
+ assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "2", true));
+ assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", true));
+ }
+
+ @Test
+ public void testBackwardRotation() {
+ final List<String> handles = Arrays.asList("0", "1", "2", "3");
+ assertEquals("0", HardwareKeyboardShortcutController.getNeighborItem(handles, "1", false));
+ assertEquals("3", HardwareKeyboardShortcutController.getNeighborItem(handles, "0", false));
+ assertEquals("2", HardwareKeyboardShortcutController.getNeighborItem(handles, "3", false));
+ }
+
+ @Test
+ public void testNotMatching() {
+ final List<String> handles = Arrays.asList("0", "1", "2", "3");
+ assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", true));
+ assertNull(HardwareKeyboardShortcutController.getNeighborItem(handles, "X", false));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index 397d7b5..7cf5bc8 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -26,8 +26,9 @@
import android.content.Context;
import android.os.Handler;
+import android.util.IntArray;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,10 +58,24 @@
private static final int TEST_UID_4 = 56926423;
private static final int TEST_UID_5 = 76421423;
+ private static final int TEST_PROC_STATE_1 = 72331;
+ private static final int TEST_PROC_STATE_2 = 792351;
+ private static final int TEST_PROC_STATE_3 = 138831;
+ private static final int TEST_PROC_STATE_4 = 23231;
+ private static final int TEST_PROC_STATE_5 = 42;
+
private static final Context sContext = InstrumentationRegistry.getTargetContext();
private final Handler mHandler = Mockito.mock(Handler.class);
private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
+ private void populateDefaultProcStates(CpuWakeupStats obj) {
+ obj.mUidProcStates.put(TEST_UID_1, TEST_PROC_STATE_1);
+ obj.mUidProcStates.put(TEST_UID_2, TEST_PROC_STATE_2);
+ obj.mUidProcStates.put(TEST_UID_3, TEST_PROC_STATE_3);
+ obj.mUidProcStates.put(TEST_UID_4, TEST_PROC_STATE_4);
+ obj.mUidProcStates.put(TEST_UID_5, TEST_PROC_STATE_5);
+ }
+
@Test
public void removesOldWakeups() {
// The xml resource doesn't matter for this test.
@@ -96,6 +111,8 @@
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 12423121;
+ populateDefaultProcStates(obj);
+
obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
// Outside the window, so should be ignored.
@@ -106,15 +123,20 @@
// Should be attributed
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3, TEST_UID_5);
- final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
assertThat(attribution).isNotNull();
assertThat(attribution.size()).isEqualTo(1);
assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_1)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_2)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_4)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
}
@Test
@@ -122,6 +144,8 @@
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 12423121;
+ populateDefaultProcStates(obj);
+
obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_WIFI_IRQ);
// Outside the window, so should be ignored.
@@ -132,15 +156,17 @@
// Should be attributed
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 3, TEST_UID_4, TEST_UID_5);
- final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
assertThat(attribution).isNotNull();
assertThat(attribution.size()).isEqualTo(1);
assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(true);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_1)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_2)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_3)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(
+ TEST_PROC_STATE_4);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
}
@Test
@@ -148,6 +174,8 @@
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123210;
+ populateDefaultProcStates(obj);
+
obj.noteWakeupTimeAndReason(wakeupTime, 4,
KERNEL_REASON_WIFI_IRQ + ":" + KERNEL_REASON_ALARM_IRQ);
@@ -173,23 +201,31 @@
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_2,
TEST_UID_5);
- final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
assertThat(attribution).isNotNull();
assertThat(attribution.size()).isEqualTo(2);
assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(true);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_1)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_2)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(
+ TEST_PROC_STATE_4);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(true);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(true);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(false);
- assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(
+ TEST_PROC_STATE_1);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(
+ TEST_PROC_STATE_2);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_3)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).indexOfKey(TEST_UID_4)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
}
@Test
@@ -206,12 +242,12 @@
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 3, TEST_UID_4,
TEST_UID_5);
- final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
assertThat(attribution).isNotNull();
assertThat(attribution.size()).isEqualTo(1);
assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
- final SparseBooleanArray uids = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
- assertThat(uids == null || uids.size() == 0).isTrue();
+ final SparseIntArray uidProcStates = attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN);
+ assertThat(uidProcStates == null || uidProcStates.size() == 0).isTrue();
}
@Test
@@ -259,4 +295,39 @@
// Any nearby activity should not end up in the attribution map.
assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
}
+
+ @Test
+ public void uidProcStateBookkeeping() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+
+ assertThat(obj.mUidProcStates.size()).isEqualTo(0);
+
+ final IntArray uids = new IntArray(87);
+ for (int i = 0; i < 87; i++) {
+ final int uid = mRandom.nextInt(1 << 20);
+ if (uids.indexOf(uid) < 0) {
+ uids.add(uid);
+ }
+ }
+
+ for (int i = 0; i < uids.size(); i++) {
+ final int uid = uids.get(i);
+ for (int j = 0; j < 43; j++) {
+ final int procState = mRandom.nextInt(1 << 15);
+ obj.noteUidProcessState(uid, procState);
+ assertThat(obj.mUidProcStates.get(uid)).isEqualTo(procState);
+ }
+ assertThat(obj.mUidProcStates.size()).isEqualTo(i + 1);
+ }
+
+ for (int i = 0; i < uids.size(); i++) {
+ obj.onUidRemoved(uids.get(i));
+ assertThat(obj.mUidProcStates.indexOfKey(uids.get(i))).isLessThan(0);
+ }
+
+ assertThat(obj.mUidProcStates.size()).isEqualTo(0);
+
+ obj.onUidRemoved(213);
+ assertThat(obj.mUidProcStates.size()).isEqualTo(0);
+ }
}
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 42d1ace..3888b9b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1638,12 +1638,6 @@
any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
mContext.getTestablePermissions().setPermission(
android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
- "true",
- false);
- Thread.sleep(300);
final String tag = "testEnqueueNotificationWithTag_FgsAddsFlags_dismissalAllowed";
@@ -1665,38 +1659,6 @@
}
@Test
- public void testEnqueueNotificationWithTag_FGSaddsFlags_dismissalNotAllowed() throws Exception {
- when(mAmi.applyForegroundServiceNotification(
- any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
- "false",
- false);
- Thread.sleep(300);
-
- final String tag = "testEnqueueNotificationWithTag_FGSaddsNoClear";
-
- Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setFlag(FLAG_FOREGROUND_SERVICE, true)
- .build();
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
- n, UserHandle.getUserHandleForUid(mUid), null, 0);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, tag,
- sbn.getId(), sbn.getNotification(), sbn.getUserId());
- waitForIdle();
-
- StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(PKG);
- assertThat(notifs[0].getNotification().flags).isEqualTo(
- FLAG_FOREGROUND_SERVICE | FLAG_CAN_COLORIZE | FLAG_NO_CLEAR | FLAG_ONGOING_EVENT);
- }
-
- @Test
public void testEnqueueNotificationWithTag_nullAction_fixed() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setContentTitle("foo")
@@ -10462,8 +10424,11 @@
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
}
- private void verifyStickyHun(Flag flag, int permissionState, boolean isSticky)
- throws Exception {
+ private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested,
+ boolean isSticky) throws Exception {
+
+ when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT,
+ PKG, mUserId)).thenReturn(appRequested);
mTestFlagResolver.setFlagOverride(flag, true);
@@ -10491,7 +10456,7 @@
throws Exception {
verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED,
+ /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
/* isSticky= */ true);
}
@@ -10500,16 +10465,25 @@
throws Exception {
verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED,
+ /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
/* isSticky= */ true);
}
@Test
+ public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun()
+ throws Exception {
+ verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
+ /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
+ /* isSticky= */ false);
+ }
+
+
+ @Test
public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
throws Exception {
verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_GRANTED,
+ /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
/* isSticky= */ false);
}
@@ -10518,7 +10492,7 @@
throws Exception {
verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED,
+ /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
/* isSticky= */ true);
}
@@ -10527,7 +10501,7 @@
throws Exception {
verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED,
+ /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
/* isSticky= */ true);
}
@@ -10536,7 +10510,7 @@
throws Exception {
verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_GRANTED,
+ /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
/* isSticky= */ true);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index f2b1dc9..397e3c1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -138,6 +138,68 @@
}
@Test
+ public void testHasRequestedPermission_otherPermission() throws Exception {
+ final String permission = "correct";
+
+ String packageName = "testHasRequestedPermission_otherPermission";
+
+ PackageInfo info = new PackageInfo();
+ info.packageName = packageName;
+ info.requestedPermissions = new String[]{"something else"};
+
+ when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+ assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isFalse();
+
+ }
+
+ @Test
+ public void testHasRequestedPermission_noPermissions() throws Exception {
+ final String permission = "correct";
+
+ String packageName = "testHasRequestedPermission_noPermissions";
+
+ PackageInfo info = new PackageInfo();
+ info.packageName = packageName;
+
+ when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+ assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isFalse();
+ }
+
+ @Test
+ public void testHasRequestedPermission_singlePermissions() throws Exception {
+ final String permission = "correct";
+
+ String packageName = "testHasRequestedPermission_twoPermissions";
+
+ PackageInfo info = new PackageInfo();
+ info.packageName = packageName;
+ info.requestedPermissions =
+ new String[]{permission};
+
+ when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+ assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isTrue();
+ }
+
+ @Test
+ public void testHasRequestedPermission_twoPermissions() throws Exception {
+ final String permission = "correct";
+
+ String packageName = "testHasRequestedPermission_twoPermissions";
+
+ PackageInfo info = new PackageInfo();
+ info.packageName = packageName;
+ info.requestedPermissions =
+ new String[]{"something else", permission};
+
+ when(mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, 0)).thenReturn(info);
+
+ assertThat(mPermissionHelper.hasRequestedPermission(permission, packageName, 0)).isTrue();
+ }
+
+ @Test
public void testGetAppsGrantedPermission_noApps() throws Exception {
int userId = 1;
ParceledListSlice<PackageInfo> infos = ParceledListSlice.emptyList();
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
new file mode 100644
index 0000000..ff2ce15
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.soundtrigger;
+
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiAudioFormatWithDefault;
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiPhrase;
+import static android.hardware.soundtrigger.ConversionUtil.aidl2apiRecognitionConfig;
+import static android.hardware.soundtrigger.ConversionUtil.api2aidlPhrase;
+import static android.hardware.soundtrigger.ConversionUtil.api2aidlRecognitionConfig;
+import static android.hardware.soundtrigger.ConversionUtil.byteArrayToSharedMemory;
+import static android.hardware.soundtrigger.ConversionUtil.sharedMemoryToByteArray;
+import static android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_GENERIC;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+ private static final String TAG = "ConversionUtilTest";
+
+ @Test
+ public void testDefaultAudioFormatConstruction() {
+ // This method should generate a real format when passed null
+ final var format = aidl2apiAudioFormatWithDefault(
+ null /** exercise default **/,
+ true /** isInput **/
+ );
+ assertNotNull(format);
+ }
+
+ @Test
+ public void testRecognitionConfigRoundTrip() {
+ final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
+ | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+ final var data = new byte[] {0x11, 0x22};
+ final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
+ keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
+ RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
+ new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
+ new ConfidenceLevel(5000, 80)});
+ keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
+ RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
+ new ConfidenceLevel(7777, 30),
+ new ConfidenceLevel(2222, 60)});
+
+ var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
+ keyphrases, data, flags);
+ assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
+ }
+
+ @Test
+ public void testByteArraySharedMemRoundTrip() {
+ final var data = new byte[] { 0x11, 0x22, 0x33, 0x44,
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
+ assertArrayEquals(data, sharedMemoryToByteArray(byteArrayToSharedMemory(data, "name"),
+ 10000000));
+
+ }
+
+ @Test
+ public void testPhraseRoundTrip() {
+ final var users = new int[] {10001, 10002};
+ final var apiphrase = new SoundTrigger.Keyphrase(17 /** id **/,
+ RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_AUTHENTICATION,
+ Locale.forLanguageTag("no_NO"),
+ "Hello Android", /** keyphrase **/
+ users);
+ assertEquals(apiphrase, aidl2apiPhrase(api2aidlPhrase(apiphrase)));
+ }
+}
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
similarity index 89%
rename from tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
rename to services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
index f49d9c9..e6a1be8 100644
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/SoundTriggerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.soundtrigger;
+package com.android.server.soundtrigger;
import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
@@ -22,6 +22,7 @@
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.os.Parcel;
import android.test.InstrumentationTestCase;
@@ -50,10 +51,7 @@
Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel);
// Verify that they are the same
- assertEquals(keyphrase.getId(), unparceled.getId());
- assertNull(unparceled.getUsers());
- assertEquals(keyphrase.getLocale(), unparceled.getLocale());
- assertEquals(keyphrase.getText(), unparceled.getText());
+ assertEquals(keyphrase, unparceled);
}
@SmallTest
@@ -115,10 +113,7 @@
KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
// Verify that they are the same
- assertEquals(ksm.getUuid(), unparceled.getUuid());
- assertNull(unparceled.getData());
- assertEquals(ksm.getType(), unparceled.getType());
- assertTrue(Arrays.equals(keyphrases, unparceled.getKeyphrases()));
+ assertEquals(ksm, unparceled);
}
@SmallTest
@@ -162,10 +157,7 @@
KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel);
// Verify that they are the same
- assertEquals(ksm.getUuid(), unparceled.getUuid());
- assertEquals(ksm.getType(), unparceled.getType());
- assertNull(unparceled.getKeyphrases());
- assertTrue(Arrays.equals(ksm.getData(), unparceled.getData()));
+ assertEquals(ksm, unparceled);
}
@SmallTest
@@ -226,7 +218,11 @@
3 /* captureDelayMs */,
4 /* capturePreambleMs */,
false /* triggerInData */,
- null /* captureFormat */,
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build(),
null /* data */,
12345678 /* halEventReceivedMillis */);
@@ -251,7 +247,11 @@
3 /* captureDelayMs */,
4 /* capturePreambleMs */,
false /* triggerInData */,
- null /* captureFormat */,
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build(),
new byte[1] /* data */,
12345678 /* halEventReceivedMillis */);
@@ -278,7 +278,11 @@
3 /* captureDelayMs */,
4 /* capturePreambleMs */,
false /* triggerInData */,
- null /* captureFormat */,
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build(),
data,
12345678 /* halEventReceivedMillis */);
@@ -335,7 +339,11 @@
3 /* captureDelayMs */,
4 /* capturePreambleMs */,
false /* triggerInData */,
- null /* captureFormat */,
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build(),
null /* data */,
null /* keyphraseExtras */,
12345678 /* halEventReceivedMillis */);
@@ -364,7 +372,11 @@
3 /* captureDelayMs */,
4 /* capturePreambleMs */,
false /* triggerInData */,
- null /* captureFormat */,
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build(),
new byte[1] /* data */,
kpExtra,
12345678 /* halEventReceivedMillis */);
@@ -409,7 +421,11 @@
3 /* captureDelayMs */,
4 /* capturePreambleMs */,
false /* triggerInData */,
- null /* captureFormat */,
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build(),
data,
kpExtra,
12345678 /* halEventReceivedMillis */);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
index 5661b12..7b7a0a3 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -16,36 +16,18 @@
package com.android.server.soundtrigger_middleware;
-import static android.hardware.soundtrigger.ConversionUtil.aidl2apiAudioFormatWithDefault;
-import static android.hardware.soundtrigger.ConversionUtil.aidl2apiPhrase;
-import static android.hardware.soundtrigger.ConversionUtil.aidl2apiRecognitionConfig;
-import static android.hardware.soundtrigger.ConversionUtil.api2aidlPhrase;
-import static android.hardware.soundtrigger.ConversionUtil.api2aidlRecognitionConfig;
-import static android.hardware.soundtrigger.ConversionUtil.byteArrayToSharedMemory;
-import static android.hardware.soundtrigger.ConversionUtil.sharedMemoryToByteArray;
-import static android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_GENERIC;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
-import static android.hardware.soundtrigger.SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
-
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import android.hardware.audio.common.V2_0.Uuid;
-import android.hardware.soundtrigger.SoundTrigger;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Locale;
-
@RunWith(AndroidJUnit4.class)
public class ConversionUtilTest {
- private static final String TAG = "ConversionUtilTest";
+ private static final String TAG = "SoundTriggerMiddlewareConversionUtilTest";
@Test
public void testUuidRoundTrip() {
@@ -62,54 +44,4 @@
Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
assertEquals(hidl, reconstructed);
}
-
- @Test
- public void testDefaultAudioFormatConstruction() {
- // This method should generate a real format when passed null
- final var format = aidl2apiAudioFormatWithDefault(
- null /** exercise default **/,
- true /** isInput **/
- );
- assertNotNull(format);
- }
-
- @Test
- public void testRecognitionConfigRoundTrip() {
- final int flags = SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION
- | SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
- final var data = new byte[] {0x11, 0x22};
- final var keyphrases = new SoundTrigger.KeyphraseRecognitionExtra[2];
- keyphrases[0] = new SoundTrigger.KeyphraseRecognitionExtra(99,
- RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_IDENTIFICATION, 13,
- new ConfidenceLevel[] {new ConfidenceLevel(9999, 50),
- new ConfidenceLevel(5000, 80)});
- keyphrases[1] = new SoundTrigger.KeyphraseRecognitionExtra(101,
- RECOGNITION_MODE_GENERIC, 8, new ConfidenceLevel[] {
- new ConfidenceLevel(7777, 30),
- new ConfidenceLevel(2222, 60)});
-
- var apiconfig = new SoundTrigger.RecognitionConfig(true, false /** must be false **/,
- keyphrases, data, flags);
- assertEquals(apiconfig, aidl2apiRecognitionConfig(api2aidlRecognitionConfig(apiconfig)));
- }
-
- @Test
- public void testByteArraySharedMemRoundTrip() {
- final var data = new byte[] { 0x11, 0x22, 0x33, 0x44,
- (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef };
- assertArrayEquals(data, sharedMemoryToByteArray(byteArrayToSharedMemory(data, "name"),
- 10000000));
-
- }
-
- @Test
- public void testPhraseRoundTrip() {
- final var users = new int[] {10001, 10002};
- final var apiphrase = new SoundTrigger.Keyphrase(17 /** id **/,
- RECOGNITION_MODE_VOICE_TRIGGER | RECOGNITION_MODE_USER_AUTHENTICATION,
- Locale.forLanguageTag("no_NO"),
- "Hello Android", /** keyphrase **/
- users);
- assertEquals(apiphrase, aidl2apiPhrase(api2aidlPhrase(apiphrase)));
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 20d410c..2914de1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -81,6 +81,17 @@
return win;
}
+ private WindowState createDreamWindow() {
+ final WindowState win = createDreamWindow(null, TYPE_BASE_APPLICATION, "dream");
+ final WindowManager.LayoutParams attrs = win.mAttrs;
+ attrs.width = MATCH_PARENT;
+ attrs.height = MATCH_PARENT;
+ attrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ attrs.format = PixelFormat.OPAQUE;
+ return win;
+ }
+
private WindowState createDimmingDialogWindow(boolean canBeImTarget) {
final WindowState win = spy(createWindow(null, TYPE_APPLICATION, "dimmingDialog"));
final WindowManager.LayoutParams attrs = win.mAttrs;
@@ -384,4 +395,25 @@
displayPolicy.requestTransientBars(mNavBarWindow, true);
assertTrue(mDisplayContent.getInsetsPolicy().isTransient(navigationBars()));
}
+
+ @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @Test
+ public void testTransientBarsSuppressedOnDreams() {
+ final WindowState win = createDreamWindow();
+
+ ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true;
+ win.mAttrs.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ win.setRequestedVisibleTypes(0, navigationBars());
+
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+ final InsetsSourceProvider navBarProvider = mNavBarWindow.getControllableInsetProvider();
+ navBarProvider.updateControlForTarget(win, false);
+ navBarProvider.getSource().setVisible(false);
+
+ displayPolicy.setCanSystemBarsBeShownByUser(true);
+ displayPolicy.requestTransientBars(mNavBarWindow, true);
+
+ assertFalse(mDisplayContent.getInsetsPolicy().isTransient(navigationBars()));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
similarity index 96%
rename from services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
rename to services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index ef20f2b..b35eceb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -42,20 +42,20 @@
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
-public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
+public class InsetsSourceProviderTest extends WindowTestsBase {
private InsetsSource mSource = new InsetsSource(
InsetsSource.createId(null, 0, statusBars()), statusBars());
- private WindowContainerInsetsSourceProvider mProvider;
+ private InsetsSourceProvider mProvider;
private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
- private WindowContainerInsetsSourceProvider mImeProvider;
+ private InsetsSourceProvider mImeProvider;
@Before
public void setUp() throws Exception {
mSource.setVisible(true);
- mProvider = new WindowContainerInsetsSourceProvider(mSource,
+ mProvider = new InsetsSourceProvider(mSource,
mDisplayContent.getInsetsStateController(), mDisplayContent);
- mImeProvider = new WindowContainerInsetsSourceProvider(mImeSource,
+ mImeProvider = new InsetsSourceProvider(mImeSource,
mDisplayContent.getInsetsStateController(), mDisplayContent);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 74fde65..ff2944a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -287,7 +287,7 @@
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
- WindowContainerInsetsSourceProvider statusBarProvider =
+ InsetsSourceProvider statusBarProvider =
getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders =
new SparseArray<>();
@@ -353,7 +353,7 @@
public void testTransientVisibilityOfFixedRotationState() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowContainerInsetsSourceProvider provider = getController()
+ final InsetsSourceProvider provider = getController()
.getOrCreateSourceProvider(ID_STATUS_BAR, statusBars());
provider.setWindowContainer(statusBar, null, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d7bf4b0..90506d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1885,6 +1885,39 @@
assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
}
+ @Test
+ public void testMoveToTopWhileVisible() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ // Start with taskB on top and taskA on bottom but both visible.
+ final Task rootTaskA = createTask(mDisplayContent);
+ final Task leafTaskA = createTaskInRootTask(rootTaskA, 0 /* userId */);
+ final Task taskB = createTask(mDisplayContent);
+ leafTaskA.setVisibleRequested(true);
+ taskB.setVisibleRequested(true);
+ // manually collect since this is a test transition and not known by transitionController.
+ transition.collect(leafTaskA);
+ rootTaskA.moveToFront("test", leafTaskA);
+
+ // All the tasks were already visible, so there shouldn't be any changes
+ ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
+ participants, changes);
+ assertTrue(targets.isEmpty());
+
+ // After collecting order changes, it should recognize that a task moved to top.
+ transition.collectOrderChanges();
+ targets = Transition.calculateTargets(participants, changes);
+ assertEquals(1, targets.size());
+
+ // Make sure the flag is set
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, mMockT);
+ assertTrue((info.getChanges().get(0).getFlags() & TransitionInfo.FLAG_MOVED_TO_TOP) != 0);
+ assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 6261e56..a1ddd57 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -484,7 +484,7 @@
windowState.mSurfaceAnimator).getAnimationType();
assertTrue(parent.isAnimating(CHILDREN));
- windowState.setControllableInsetProvider(mock(WindowContainerInsetsSourceProvider.class));
+ windowState.setControllableInsetProvider(mock(InsetsSourceProvider.class));
assertFalse(parent.isAnimating(CHILDREN));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0d7cdc8..7e3ec55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.AppOpsManager.OP_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -467,6 +468,12 @@
return createWindow(null, type, activity, name);
}
+ WindowState createDreamWindow(WindowState parent, int type, String name) {
+ final WindowToken token = createWindowToken(
+ mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, type);
+ return createWindow(parent, type, token, name);
+ }
+
// TODO: Move these calls to a builder?
WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
IWindow iwindow) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java
index 86c4bbf..37a325e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/FakeSoundTriggerHal.java
@@ -276,16 +276,15 @@
// for our clients.
mGlobalEventSession = new IInjectGlobalEvent.Stub() {
/**
- * Overrides IInjectGlobalEvent method.
* Simulate a HAL process restart. This method is not included in regular HAL interface,
* since the entire process is restarted by sending a signal.
* Since we run in-proc, we must offer an explicit restart method.
* oneway
*/
@Override
- public void triggerRestart() throws RemoteException {
+ public void triggerRestart() {
synchronized (FakeSoundTriggerHal.this.mLock) {
- if (mIsDead) throw new DeadObjectException();
+ if (mIsDead) return;
mIsDead = true;
mInjectionDispatcher.wrap((ISoundTriggerInjection cb) ->
cb.onRestarted(this));
@@ -305,15 +304,15 @@
}
}
- /**
- * Overrides IInjectGlobalEvent method.
- * oneway
- */
+ // oneway
@Override
public void setResourceContention(boolean isResourcesContended,
- IAcknowledgeEvent callback) throws RemoteException {
+ IAcknowledgeEvent callback) {
synchronized (FakeSoundTriggerHal.this.mLock) {
- if (mIsDead) throw new DeadObjectException();
+ // oneway, so don't throw on death
+ if (mIsDead || mIsResourceContended == isResourcesContended) {
+ return;
+ }
mIsResourceContended = isResourcesContended;
// Introducing contention is the only injection which can't be
// observed by the ST client.
@@ -325,7 +324,19 @@
}
}
}
+
+ // oneway
+ @Override
+ public void triggerOnResourcesAvailable() {
+ synchronized (FakeSoundTriggerHal.this.mLock) {
+ // oneway, so don't throw on death
+ if (mIsDead) return;
+ mGlobalCallbackDispatcher.wrap((ISoundTriggerHwGlobalCallback cb) ->
+ cb.onResourcesAvailable());
+ }
+ }
};
+
// Register the global event injection interface
mInjectionDispatcher.wrap((ISoundTriggerInjection cb)
-> cb.registerGlobalEventInjection(mGlobalEventSession));
@@ -465,7 +476,9 @@
if (session == null) {
Slog.wtf(TAG, "Attempted to start recognition with invalid handle");
}
-
+ if (mIsResourceContended) {
+ throw new ServiceSpecificException(Status.RESOURCE_CONTENTION);
+ }
if (session.getIsUnloaded()) {
// TODO(b/274470274) this is a deficiency in the existing HAL API, there is no way
// to handle this race gracefully
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 2f8d17d..4c134af 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -35,7 +35,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -463,7 +463,7 @@
printObject(originatorIdentity),
printArgs(args),
printObject(retVal));
- Log.i(TAG, message);
+ Slog.i(TAG, message);
appendMessage(message);
}
@@ -474,7 +474,7 @@
object,
printObject(originatorIdentity),
printArgs(args));
- Log.i(TAG, message);
+ Slog.i(TAG, message);
appendMessage(message);
}
@@ -486,7 +486,7 @@
object,
printObject(originatorIdentity),
printArgs(args));
- Log.e(TAG, message, ex);
+ Slog.e(TAG, message, ex);
appendMessage(message + " " + ex.toString());
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index fa4c9b2..ff378ba 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -43,6 +43,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -325,11 +326,19 @@
}
private void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
+ // This is purposefully not synchronized with broadcastToCallbacksLocked because the
+ // list of callbacks to notify is copied over from the original list modified here. I also
+ // do not want to risk introducing a deadlock by using the same mCallbacks Object to
+ // synchronize on outgoing and incoming operations.
mCallbacks.register(c);
updateNewCallbackWithState(c);
}
private void removeRegistrationCallback(IImsRegistrationCallback c) {
+ // This is purposefully not synchronized with broadcastToCallbacksLocked because the
+ // list of callbacks to notify is copied over from the original list modified here. I also
+ // do not want to risk introducing a deadlock by using the same mCallbacks Object to
+ // synchronize on outgoing and incoming operations.
mCallbacks.unregister(c);
}
@@ -420,7 +429,7 @@
@SystemApi
public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
- mCallbacks.broadcastAction((c) -> {
+ broadcastToCallbacksLocked((c) -> {
try {
c.onRegistered(attributes);
} catch (RemoteException e) {
@@ -449,7 +458,7 @@
@SystemApi
public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
- mCallbacks.broadcastAction((c) -> {
+ broadcastToCallbacksLocked((c) -> {
try {
c.onRegistering(attributes);
} catch (RemoteException e) {
@@ -507,7 +516,7 @@
updateToDisconnectedState(info, suggestedAction, imsRadioTech);
// ImsReasonInfo should never be null.
final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
- mCallbacks.broadcastAction((c) -> {
+ broadcastToCallbacksLocked((c) -> {
try {
c.onDeregistered(reasonInfo, suggestedAction, imsRadioTech);
} catch (RemoteException e) {
@@ -569,7 +578,7 @@
updateToDisconnectedState(info, suggestedAction, imsRadioTech);
// ImsReasonInfo should never be null.
final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
- mCallbacks.broadcastAction((c) -> {
+ broadcastToCallbacksLocked((c) -> {
try {
c.onDeregisteredWithDetails(reasonInfo, suggestedAction, imsRadioTech, details);
} catch (RemoteException e) {
@@ -591,7 +600,7 @@
public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
ImsReasonInfo info) {
final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
- mCallbacks.broadcastAction((c) -> {
+ broadcastToCallbacksLocked((c) -> {
try {
c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
} catch (RemoteException e) {
@@ -614,7 +623,20 @@
mUris = ArrayUtils.cloneOrNull(uris);
mUrisSet = true;
}
- mCallbacks.broadcastAction((c) -> onSubscriberAssociatedUriChanged(c, uris));
+ broadcastToCallbacksLocked((c) -> onSubscriberAssociatedUriChanged(c, uris));
+ }
+
+ /**
+ * Broadcast the specified operation in a synchronized manner so that multiple threads do not
+ * try to call broadcast at the same time, which will generate an error.
+ * @param c The Consumer lambda method containing the callback to call.
+ */
+ private void broadcastToCallbacksLocked(Consumer<IImsRegistrationCallback> c) {
+ // One broadcast can happen at a time, so synchronize threads so only one
+ // beginBroadcast/endBroadcast happens at a time.
+ synchronized (mCallbacks) {
+ mCallbacks.broadcastAction(c);
+ }
}
private void onSubscriberAssociatedUriChanged(IImsRegistrationCallback callback, Uri[] uris) {
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
index 2954c2d..e229f05 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
@@ -18,7 +18,7 @@
import android.telephony.satellite.SatelliteDatagram;
-import com.android.internal.telephony.ILongConsumer;
+import com.android.internal.telephony.IVoidConsumer;
/**
* Interface for satellite datagrams callback.
@@ -31,10 +31,10 @@
* @param datagramId An id that uniquely identifies incoming datagram.
* @param datagram Datagram received from satellite.
* @param pendingCount Number of datagrams yet to be received from satellite.
- * @param callback This callback will be used by datagram receiver app to send received
- * datagramId to Telephony. If the callback is not received within five minutes,
- * Telephony will resend the datagram.
+ * @param callback This callback will be used by datagram receiver app to to inform
+ * Telephony that datagram is received. If the callback is not received
+ * within five minutes, Telephony will resend the datagram.
*/
void onSatelliteDatagramReceived(long datagramId, in SatelliteDatagram datagram,
- int pendingCount, ILongConsumer callback);
+ int pendingCount, IVoidConsumer callback);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index d8a6faf..d0409bf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
-import com.android.internal.telephony.ILongConsumer;
+import java.util.function.Consumer;
/**
* A callback class for listening to satellite datagrams.
@@ -33,11 +33,11 @@
* @param datagramId An id that uniquely identifies incoming datagram.
* @param datagram Datagram to be received over satellite.
* @param pendingCount Number of datagrams yet to be received by the app.
- * @param callback This callback will be used by datagram receiver app to send received
- * datagramId to Telephony. If the callback is not received within five minutes,
- * Telephony will resend the datagram.
+ * @param callback This callback will be used by datagram receiver app to inform Telephony
+ * that they received the datagram. If the callback is not received within
+ * five minutes, Telephony will resend the datagram.
*/
@UnsupportedAppUsage
void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
- int pendingCount, @NonNull ILongConsumer callback);
+ int pendingCount, @NonNull Consumer<Void> callback);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7d82fd8..20f9bc8b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -37,7 +37,7 @@
import android.telephony.TelephonyFrameworkInitializer;
import com.android.internal.telephony.IIntegerConsumer;
-import com.android.internal.telephony.ILongConsumer;
+import com.android.internal.telephony.IVoidConsumer;
import com.android.internal.telephony.ITelephony;
import com.android.telephony.Rlog;
@@ -862,7 +862,7 @@
*
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
- * @param regionId The region ID for the device's current location.
+ * @param provisionData Data from the provisioning app that can be used by provisioning server
* @param cancellationSignal The optional signal used by the caller to cancel the provision
* request. Even when the cancellation is signaled, Telephony will
* still trigger the callback to return the result of this request.
@@ -874,13 +874,14 @@
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@UnsupportedAppUsage
- public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
+ public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(token);
Objects.requireNonNull(executor);
Objects.requireNonNull(resultListener);
+ Objects.requireNonNull(provisionData);
ICancellationSignal cancelRemote = null;
try {
@@ -893,7 +894,7 @@
() -> resultListener.accept(result)));
}
};
- cancelRemote = telephony.provisionSatelliteService(mSubId, token, regionId,
+ cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
errorCallback);
} else {
throw new IllegalStateException("telephony service is null.");
@@ -1186,10 +1187,22 @@
@Override
public void onSatelliteDatagramReceived(long datagramId,
@NonNull SatelliteDatagram datagram, int pendingCount,
- @NonNull ILongConsumer ack) {
+ @NonNull IVoidConsumer internalAck) {
+ Consumer<Void> externalAck = new Consumer<Void>() {
+ @Override
+ public void accept(Void result) {
+ try {
+ internalAck.accept();
+ } catch (RemoteException e) {
+ logd("onSatelliteDatagramReceived "
+ + "RemoteException: " + e);
+ }
+ }
+ };
+
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> callback.onSatelliteDatagramReceived(
- datagramId, datagram, pendingCount, ack)));
+ datagramId, datagram, pendingCount, externalAck)));
}
};
sSatelliteDatagramCallbackMap.put(callback, internalCallback);
@@ -1244,7 +1257,7 @@
* This method requests modem to check if there are any pending datagrams to be received over
* satellite. If there are any incoming datagrams, they will be received via
* {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int,
- * ILongConsumer)}
+ * Consumer)} )}
*
* @param executor The executor on which the result listener will be called.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index a780cb9..ea4e2e2 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -67,6 +67,15 @@
in IIntegerConsumer resultCallback);
/**
+ * Allow cellular modem scanning while satellite mode is on.
+ * @param enabled {@code true} to enable cellular modem while satellite mode is on
+ * and {@code false} to disable
+ * @param errorCallback The callback to receive the error code result of the operation.
+ */
+ void enableCellularModemWhileSatelliteModeIsOn(in boolean enabled,
+ in IIntegerConsumer errorCallback);
+
+ /**
* Request to enable or disable the satellite modem and demo mode. If the satellite modem
* is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
* this may also re-enable the cellular modem.
@@ -194,7 +203,7 @@
*
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
- * @param regionId The region ID for the device's current location.
+ * @param provisionData Data from the provisioning app that can be used by provisioning server
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -210,7 +219,7 @@
* SatelliteError:REQUEST_ABORTED
* SatelliteError:NETWORK_TIMEOUT
*/
- void provisionSatelliteService(in String token, in String regionId,
+ void provisionSatelliteService(in String token, in byte[] provisionData,
in IIntegerConsumer resultCallback);
/**
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index debb394..17d026c 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -79,6 +79,15 @@
}
@Override
+ public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+ IIntegerConsumer errorCallback) throws RemoteException {
+ executeMethodAsync(
+ () -> SatelliteImplBase.this
+ .enableCellularModemWhileSatelliteModeIsOn(enabled, errorCallback),
+ "enableCellularModemWhileSatelliteModeIsOn");
+ }
+
+ @Override
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
@@ -132,11 +141,11 @@
}
@Override
- public void provisionSatelliteService(String token, String regionId,
+ public void provisionSatelliteService(String token, byte[] provisionData,
IIntegerConsumer errorCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
- .provisionSatelliteService(token, regionId, errorCallback),
+ .provisionSatelliteService(token, provisionData, errorCallback),
"provisionSatelliteService");
}
@@ -261,6 +270,17 @@
}
/**
+ * Allow cellular modem scanning while satellite mode is on.
+ * @param enabled {@code true} to enable cellular modem while satellite mode is on
+ * and {@code false} to disable
+ * @param errorCallback The callback to receive the error code result of the operation.
+ */
+ public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+ @NonNull IIntegerConsumer errorCallback) {
+ // stub implementation
+ }
+
+ /**
* Request to enable or disable the satellite modem and demo mode. If the satellite modem is
* enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
* this may also re-enable the cellular modem.
@@ -401,7 +421,8 @@
*
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
- * @param regionId The region ID for the device's current location.
+ * @param provisionData Data from the provisioning app that can be used by provisioning
+ * server
* @param errorCallback The callback to receive the error code result of the operation.
*
* Valid error codes returned:
@@ -417,7 +438,7 @@
* SatelliteError:REQUEST_ABORTED
* SatelliteError:NETWORK_TIMEOUT
*/
- public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
+ public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
@NonNull IIntegerConsumer errorCallback) {
// stub implementation
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index b890005..cbdf38ae 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2823,15 +2823,15 @@
* @param subId The subId of the subscription to be provisioned.
* @param token The token to be used as a unique identifier for provisioning with satellite
* gateway.
- * @param regionId The region ID for the device's current location.
+ * @provisionData Data from the provisioning app that can be used by provisioning server
* @param callback The callback to get the result of the request.
*
* @return The signal transport used by callers to cancel the provision request.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- ICancellationSignal provisionSatelliteService(int subId, in String token, in String regionId,
- in IIntegerConsumer callback);
+ ICancellationSignal provisionSatelliteService(int subId, in String token,
+ in byte[] provisionData, in IIntegerConsumer callback);
/**
* Unregister the subscription with the satellite provider.
diff --git a/telephony/java/com/android/internal/telephony/IVoidConsumer.aidl b/telephony/java/com/android/internal/telephony/IVoidConsumer.aidl
new file mode 100644
index 0000000..b5557fd
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IVoidConsumer.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.internal.telephony;
+
+ /**
+ * Copies consumer pattern for an operation that requires void result from another process to
+ * finish.
+ */
+ oneway interface IVoidConsumer {
+ void accept();
+ }
\ No newline at end of file
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index fef5211..4ba538e 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -50,6 +50,9 @@
"platform-test-annotations",
"wm-flicker-window-extensions",
],
+ data: [
+ ":FlickerTestApp",
+ ],
}
java_library {
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 9dc4bf0..314b9e4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -280,21 +280,28 @@
*
* @param originalLayer
* ```
+ *
* Layer that should be visible at the start
+ *
* @param newLayer Layer that should be visible at the end
* @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored
+ *
* ```
* when checking the transition. If true we will not fail the assertion if a rotation layer is
* visible to fill the gap between the [originalLayer] being visible and the [newLayer] being
* visible.
* @param ignoreSnapshot
* ```
+ *
* If the snapshot layer should be ignored during the transition
+ *
* ```
* (useful mostly for app launch)
* @param ignoreSplashscreen
* ```
+ *
* If the splashscreen layer should be ignored during the transition.
+ *
* ```
* If true then we will allow for a splashscreen to be shown before the layer is shown,
* otherwise we won't and the layer must appear immediately.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
index 7aea05d..fde0981 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -71,7 +71,7 @@
* Open Assistance UI.
*
* @param longpress open the UI by long pressing power button. Otherwise open the UI through
- * vioceinteraction shell command directly.
+ * vioceinteraction shell command directly.
*/
@JvmOverloads
fun openUI(longpress: Boolean = false) {
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 79c048a..d4f48fe 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
@@ -60,7 +60,6 @@
*
* @param wmHelper Helper used to get window region.
* @param direction UiAutomator Direction enum to indicate the swipe direction.
- *
* @return true if the swipe operation is successful.
*/
fun switchToPreviousAppByQuickSwitchGesture(
@@ -96,7 +95,6 @@
* @param packageName The targe application's package name.
* @param identifier The resource id of the target object.
* @param timeout The timeout duration in milliseconds.
- *
* @return true if the target object exists.
*/
@JvmOverloads
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 e497ae4..a72c12d 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
@@ -57,18 +57,16 @@
obj.click()
}
- /**
- * Drags the PIP window to the provided final coordinates without releasing the pointer.
- */
- fun dragPipWindowAwayFromEdgeWithoutRelease(
- wmHelper: WindowManagerStateHelper,
- steps: Int
- ) {
+ /** Drags the PIP window to the provided final coordinates without releasing the pointer. */
+ fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) {
val initWindowRect = getWindowRect(wmHelper).clone()
// initial pointer at the center of the window
- val initialCoord = GestureHelper.Tuple(initWindowRect.centerX().toFloat(),
- initWindowRect.centerY().toFloat())
+ val initialCoord =
+ GestureHelper.Tuple(
+ initWindowRect.centerX().toFloat(),
+ initWindowRect.centerY().toFloat()
+ )
// the offset to the right (or left) of the window center to drag the window to
val offset = 50
@@ -76,8 +74,8 @@
// the actual final x coordinate with the offset included;
// if the pip window is closer to the right edge of the display the offset is negative
// otherwise the offset is positive
- val endX = initWindowRect.centerX() +
- offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
+ val endX =
+ initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
// drag to the final coordinate
@@ -106,7 +104,8 @@
val startX = initWindowRect.centerX()
val y = initWindowRect.centerY()
- val displayRect = wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ val displayRect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
?: throw IllegalStateException("Default display is null")
// the offset to the right (or left) of the display center to drag the window to
@@ -129,7 +128,8 @@
fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean {
val windowRect = getWindowRect(wmHelper)
- val displayRect = wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ val displayRect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
?: throw IllegalStateException("Default display is null")
return windowRect.centerX() > displayRect.centerX()
@@ -301,9 +301,7 @@
closePipWindow(WindowManagerStateHelper(mInstrumentation))
}
- /**
- * Returns the pip window bounds.
- */
+ /** Returns the pip window bounds. */
fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
val windowRegion = wmHelper.getWindowRegion(this)
require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
index 432df20..c355e27 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
@@ -39,4 +39,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index a4e4b6f..df9d33b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -40,6 +40,7 @@
* Don't show if this is not explicitly requested by the user and the input method
* is fullscreen. That would be too disruptive.
* ```
+ *
* More details on b/190352379
*
* To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest`
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index e85da1f..7954dd1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -40,6 +40,7 @@
* Don't show if this is not explicitly requested by the user and the input method
* is fullscreen. That would be too disruptive.
* ```
+ *
* More details on b/190352379
*
* To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest`
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index e2d6dbf..2fff001 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -20,7 +20,6 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -29,7 +28,6 @@
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,17 +63,9 @@
@Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
- }
-
@FlakyTest(bugId = 246284124)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 1fee20d..a3fb73b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -16,20 +16,17 @@
package com.android.server.wm.flicker.ime
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -74,17 +71,9 @@
@Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
- @FlakyTest(bugId = 240918620)
- @Test
- fun snapshotStartingWindowLayerCoversExactlyOnApp() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
- }
-
@Presubmit
@Test
- fun snapshotStartingWindowLayerCoversExactlyOnApp_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ fun snapshotStartingWindowLayerCoversExactlyOnApp() {
flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
index efda0ff..e1aa418 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
@@ -40,4 +40,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index daee332..690ed53 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -20,13 +20,13 @@
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import org.junit.FixMethodOrder
@@ -86,9 +86,7 @@
}
}
/** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 7514c9b..866e858 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -19,14 +19,14 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
-import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
import org.junit.FixMethodOrder
import org.junit.Test
@@ -44,20 +44,27 @@
* Make sure no apps are running on the device
* Launch an app [testApp] that automatically displays IME and wait animation to complete
* ```
+ *
* To run only the presubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
+ *
* To run only the postsubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
+ *
* To run only the flaky assertions add: `--
+ *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index a57aa5b..6f22589 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -19,7 +19,6 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -29,6 +28,7 @@
import android.view.WindowInsets.Type.statusBars
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
index cffc05d..8891d26 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
@@ -45,4 +45,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 9ea12a9..231d0d7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -19,8 +19,6 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.component.ComponentNameMatcher
import android.tools.common.traces.ConditionsFactory
-import android.tools.device.flicker.isShellTransitionsEnabled
-import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -28,6 +26,7 @@
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
import org.junit.Assume
@@ -101,16 +100,6 @@
flicker.navBarLayerIsVisibleAtStartAndEnd()
}
- /** Bars are expected to be hidden while entering overview in landscape (b/227189877) */
- @Presubmit
- @Test
- fun navBarLayerIsVisibleAtStartAndEndGestural() {
- Assume.assumeFalse(flicker.scenario.isTablet)
- Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.navBarLayerIsVisibleAtStartAndEnd()
- }
-
/**
* In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
* this is fixed and the nav bar shows as invisible
@@ -121,7 +110,6 @@
Assume.assumeFalse(flicker.scenario.isTablet)
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -186,25 +174,15 @@
@Presubmit
@Test
- fun statusBarLayerIsInvisibleInLandscapeShell() {
+ fun statusBarLayerIsInvisibleInLandscape() {
Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
Assume.assumeFalse(flicker.scenario.isTablet)
- Assume.assumeTrue(isShellTransitionsEnabled)
flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
@Presubmit
@Test
- fun statusBarLayerIsVisibleInLandscapeLegacy() {
- Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(flicker.scenario.isTablet)
- Assume.assumeFalse(isShellTransitionsEnabled)
- flicker.statusBarLayerIsVisibleAtStartAndEnd()
- }
-
- @Presubmit
- @Test
fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
flicker.assertLayersStart {
isVisible(ComponentNameMatcher.IME)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index e8f9aa3..3c577ac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -44,6 +44,7 @@
* Launch a secondary activity within the app
* Close the secondary activity back to the initial one
* ```
+ *
* Notes:
* ```
* 1. Part of the test setup occurs automatically via
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 05abf9f..360a233 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -39,6 +39,7 @@
* Make sure no apps are running on the device
* Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 63ffee6..12c0874 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -135,14 +135,15 @@
}
/**
- * Ensures that posted notifications will be visible on the lockscreen and not
- * suppressed due to being marked as seen.
+ * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+ * due to being marked as seen.
*/
@ClassRule
@JvmField
- val disableUnseenNotifFilterRule = SettingOverrideRule(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- /* value= */ "0",
- )
+ val disableUnseenNotifFilterRule =
+ SettingOverrideRule(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ /* value= */ "0",
+ )
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index a221ef6..222caed 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -150,14 +150,15 @@
}
/**
- * Ensures that posted notifications will be visible on the lockscreen and not
- * suppressed due to being marked as seen.
+ * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+ * due to being marked as seen.
*/
@ClassRule
@JvmField
- val disableUnseenNotifFilterRule = SettingOverrideRule(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- /* value= */ "0",
- )
+ val disableUnseenNotifFilterRule =
+ SettingOverrideRule(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ /* value= */ "0",
+ )
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
index d90b3ca..43d28fa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
@@ -42,4 +42,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 3fccd12..6fa65fd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -22,12 +22,10 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA
import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.SPLASH_SCREEN
import android.tools.common.datatypes.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
import android.tools.common.datatypes.component.ComponentSplashScreenMatcher
import android.tools.common.datatypes.component.IComponentMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -38,7 +36,6 @@
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -123,22 +120,8 @@
/** Checks that a color background is visible while the task transition is occurring. */
@Presubmit
@Test
- fun transitionHasColorBackground_legacy() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- transitionHasColorBackground(DEFAULT_TASK_DISPLAY_AREA)
- }
-
- /** Checks that a color background is visible while the task transition is occurring. */
- @Presubmit
- @Test
- fun transitionHasColorBackground_shellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- transitionHasColorBackground(ComponentNameMatcher("", "Animation Background"))
- }
-
- private fun transitionHasColorBackground(backgroundColorLayer: IComponentMatcher) {
- Assume.assumeTrue(isShellTransitionsEnabled)
-
+ fun transitionHasColorBackground() {
+ val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
flicker.assertLayers {
this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
@@ -221,9 +204,10 @@
.getIdentifier("image_wallpaper_component", "string", "android")
// frameworks/base/core/res/res/values/config.xml returns package plus class name,
// but wallpaper layer has only class name
- val rawComponentMatcher = ComponentNameMatcher.unflattenFromString(
- instrumentation.targetContext.resources.getString(resourceId)
- )
+ val rawComponentMatcher =
+ ComponentNameMatcher.unflattenFromString(
+ instrumentation.targetContext.resources.getString(resourceId)
+ )
return ComponentNameMatcher(rawComponentMatcher.className)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 63299cb..d49f035 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -22,7 +22,6 @@
import android.tools.common.Rotation
import android.tools.common.datatypes.Rect
import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.isShellTransitionsEnabled
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -30,7 +29,6 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -262,17 +260,9 @@
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
- }
-
@FlakyTest(bugId = 246285528)
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry_shellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
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 4a4180b..fe789a7 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
@@ -39,6 +39,7 @@
* 0 -> 90 degrees
* 90 -> 0 degrees
* ```
+ *
* Actions:
* ```
* Launch an app (via intent)
@@ -47,22 +48,29 @@
* Change device orientation
* Stop tracing
* ```
+ *
* To run this test: `atest FlickerTests:ChangeAppRotationTest`
*
* To run only the presubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
+ *
* To run only the postsubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
+ *
* To run only the flaky assertions add: `--
+ *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
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 17b3b2b..4d010f3 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
@@ -45,6 +45,7 @@
* 90 -> 0 degrees
* 90 -> 0 degrees (with starved UI thread)
* ```
+ *
* Actions:
* ```
* Launch an app in fullscreen and supporting seamless rotation (via intent)
@@ -53,22 +54,29 @@
* Change device orientation
* Stop tracing
* ```
+ *
* To run this test: `atest FlickerTests:SeamlessAppRotationTest`
*
* To run only the presubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
+ *
* To run only the postsubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
+ *
* To run only the flaky assertions add: `--
+ *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp
index de86498..5f03aa2 100644
--- a/tests/OdmApps/Android.bp
+++ b/tests/OdmApps/Android.bp
@@ -26,4 +26,7 @@
srcs: ["src/**/*.java"],
libs: ["tradefed"],
test_suites: ["device-tests"],
+ data: [
+ ":TestOdmApp",
+ ],
}
diff --git a/tests/SoundTriggerTests/Android.mk b/tests/SoundTriggerTests/Android.mk
deleted file mode 100644
index cc0fa1c..0000000
--- a/tests/SoundTriggerTests/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-ifeq ($(SOUND_TRIGGER_USE_STUB_MODULE), 1)
- LOCAL_SRC_FILES := $(call all-subdir-java-files)
- LOCAL_PRIVILEGED_MODULE := true
- LOCAL_CERTIFICATE := platform
- TARGET_OUT_DATA_APPS_PRIVILEGED := $(TARGET_OUT_DATA)/priv-app
-else
- LOCAL_SRC_FILES := src/android/hardware/soundtrigger/SoundTriggerTest.java
-endif
-
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-
-LOCAL_PACKAGE_NAME := SoundTriggerTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTests/AndroidManifest.xml b/tests/SoundTriggerTests/AndroidManifest.xml
deleted file mode 100644
index f7454c7..0000000
--- a/tests/SoundTriggerTests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.hardware.soundtrigger">
- <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
- <uses-permission android:name="android.permission.INTERNET" />
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.hardware.soundtrigger"
- android:label="Tests for android.hardware.soundtrigger" />
-</manifest>
diff --git a/tests/SoundTriggerTests/OWNERS b/tests/SoundTriggerTests/OWNERS
deleted file mode 100644
index 1e41886..0000000
--- a/tests/SoundTriggerTests/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /media/java/android/media/soundtrigger/OWNERS
diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
deleted file mode 100644
index 2c3592c..0000000
--- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2014 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.hardware.soundtrigger;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.media.soundtrigger.SoundTriggerManager;
-import android.os.ParcelUuid;
-import android.os.ServiceManager;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.app.ISoundTriggerService;
-
-import org.mockito.MockitoAnnotations;
-
-import java.io.DataOutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Random;
-import java.util.UUID;
-
-public class GenericSoundModelTest extends AndroidTestCase {
- static final int MSG_DETECTION_ERROR = -1;
- static final int MSG_DETECTION_RESUME = 0;
- static final int MSG_DETECTION_PAUSE = 1;
- static final int MSG_KEYPHRASE_TRIGGER = 2;
- static final int MSG_GENERIC_TRIGGER = 4;
-
- private Random random = new Random();
- private HashSet<UUID> loadedModelUuids;
- private ISoundTriggerService soundTriggerService;
- private SoundTriggerManager soundTriggerManager;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
-
- Context context = getContext();
- soundTriggerService = ISoundTriggerService.Stub.asInterface(
- ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
- soundTriggerManager = (SoundTriggerManager) context.getSystemService(
- Context.SOUND_TRIGGER_SERVICE);
-
- loadedModelUuids = new HashSet<UUID>();
- }
-
- @Override
- public void tearDown() throws Exception {
- for (UUID modelUuid : loadedModelUuids) {
- soundTriggerService.deleteSoundModel(new ParcelUuid(modelUuid));
- }
- super.tearDown();
- }
-
- GenericSoundModel new_sound_model() {
- // Create sound model
- byte[] data = new byte[1024];
- random.nextBytes(data);
- UUID modelUuid = UUID.randomUUID();
- UUID mVendorUuid = UUID.randomUUID();
- return new GenericSoundModel(modelUuid, mVendorUuid, data);
- }
-
- @SmallTest
- public void testUpdateGenericSoundModel() throws Exception {
- GenericSoundModel model = new_sound_model();
-
- // Update sound model
- soundTriggerService.updateSoundModel(model);
- loadedModelUuids.add(model.getUuid());
-
- // Confirm it was updated
- GenericSoundModel returnedModel =
- soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid()));
- assertEquals(model, returnedModel);
- }
-
- @SmallTest
- public void testDeleteGenericSoundModel() throws Exception {
- GenericSoundModel model = new_sound_model();
-
- // Update sound model
- soundTriggerService.updateSoundModel(model);
- loadedModelUuids.add(model.getUuid());
-
- // Delete sound model
- soundTriggerService.deleteSoundModel(new ParcelUuid(model.getUuid()));
- loadedModelUuids.remove(model.getUuid());
-
- // Confirm it was deleted
- GenericSoundModel returnedModel =
- soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid()));
- assertEquals(null, returnedModel);
- }
-
- @LargeTest
- public void testStartStopGenericSoundModel() throws Exception {
- GenericSoundModel model = new_sound_model();
-
- boolean captureTriggerAudio = true;
- boolean allowMultipleTriggers = true;
- RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- null, null);
- TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
-
- // Update and start sound model recognition
- soundTriggerService.updateSoundModel(model);
- loadedModelUuids.add(model.getUuid());
- int r = soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback,
- config);
- assertEquals("Could Not Start Recognition with code: " + r,
- android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
-
- // Stop recognition
- r = soundTriggerService.stopRecognition(new ParcelUuid(model.getUuid()), spyCallback);
- assertEquals("Could Not Stop Recognition with code: " + r,
- android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
- }
-
- @LargeTest
- public void testTriggerGenericSoundModel() throws Exception {
- GenericSoundModel model = new_sound_model();
-
- boolean captureTriggerAudio = true;
- boolean allowMultipleTriggers = true;
- RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- null, null);
- TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
-
- // Update and start sound model
- soundTriggerService.updateSoundModel(model);
- loadedModelUuids.add(model.getUuid());
- soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback, config);
-
- // Send trigger to stub HAL
- Socket socket = new Socket(InetAddress.getLocalHost(), 14035);
- DataOutputStream out = new DataOutputStream(socket.getOutputStream());
- out.writeBytes("trig " + model.getUuid().toString() + "\r\n");
- out.flush();
- socket.close();
-
- // Verify trigger was received
- verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any());
- }
-
- /**
- * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping
- * recognition. Intended to find unexpected errors that occur in unexpected states.
- */
- @LargeTest
- public void testFuzzGenericSoundModel() throws Exception {
- int numModels = 2;
-
- final int STATUS_UNLOADED = 0;
- final int STATUS_LOADED = 1;
- final int STATUS_STARTED = 2;
-
- class ModelInfo {
- int status;
- GenericSoundModel model;
-
- public ModelInfo(GenericSoundModel model, int status) {
- this.status = status;
- this.model = model;
- }
- }
-
- Random predictableRandom = new Random(100);
-
- ArrayList modelInfos = new ArrayList<ModelInfo>();
- for(int i=0; i<numModels; i++) {
- // Create sound model
- byte[] data = new byte[1024];
- predictableRandom.nextBytes(data);
- UUID modelUuid = UUID.randomUUID();
- UUID mVendorUuid = UUID.randomUUID();
- GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
- ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED);
- modelInfos.add(modelInfo);
- }
-
- boolean captureTriggerAudio = true;
- boolean allowMultipleTriggers = true;
- RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- null, null);
- TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback());
-
-
- int numOperationsToRun = 100;
- for(int i=0; i<numOperationsToRun; i++) {
- // Select a random model
- int modelInfoIndex = predictableRandom.nextInt(modelInfos.size());
- ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex);
-
- // Perform a random operation
- int operation = predictableRandom.nextInt(5);
-
- if (operation == 0 && modelInfo.status == STATUS_UNLOADED) {
- // Update and start sound model
- soundTriggerService.updateSoundModel(modelInfo.model);
- loadedModelUuids.add(modelInfo.model.getUuid());
- modelInfo.status = STATUS_LOADED;
- } else if (operation == 1 && modelInfo.status == STATUS_LOADED) {
- // Start the sound model
- int r = soundTriggerService.startRecognition(new ParcelUuid(
- modelInfo.model.getUuid()),
- spyCallback, config);
- assertEquals("Could Not Start Recognition with code: " + r,
- android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
- modelInfo.status = STATUS_STARTED;
- } else if (operation == 2 && modelInfo.status == STATUS_STARTED) {
- // Send trigger to stub HAL
- Socket socket = new Socket(InetAddress.getLocalHost(), 14035);
- DataOutputStream out = new DataOutputStream(socket.getOutputStream());
- out.writeBytes("trig " + modelInfo.model.getUuid() + "\r\n");
- out.flush();
- socket.close();
-
- // Verify trigger was received
- verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any());
- reset(spyCallback);
- } else if (operation == 3 && modelInfo.status == STATUS_STARTED) {
- // Stop recognition
- int r = soundTriggerService.stopRecognition(new ParcelUuid(
- modelInfo.model.getUuid()),
- spyCallback);
- assertEquals("Could Not Stop Recognition with code: " + r,
- android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r);
- modelInfo.status = STATUS_LOADED;
- } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) {
- // Delete sound model
- soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.getUuid()));
- loadedModelUuids.remove(modelInfo.model.getUuid());
-
- // Confirm it was deleted
- GenericSoundModel returnedModel = soundTriggerService.getSoundModel(
- new ParcelUuid(modelInfo.model.getUuid()));
- assertEquals(null, returnedModel);
- modelInfo.status = STATUS_UNLOADED;
- }
- }
- }
-
- public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub {
- @Override
- public void onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent) {
- }
-
- @Override
- public void onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent) {
- }
-
- @Override
- public void onError(int status) {
- }
-
- @Override
- public void onRecognitionPaused() {
- }
-
- @Override
- public void onRecognitionResumed() {
- }
- }
-}
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index ffde8c7..23efe54 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -55,6 +55,7 @@
"cts-install-lib-host",
],
data: [
+ ":StagedInstallInternalTestApp",
":apex.apexd_test",
":com.android.apex.apkrollback.test_v1",
":com.android.apex.apkrollback.test_v2",
diff --git a/tests/SystemMemoryTest/host/Android.bp b/tests/SystemMemoryTest/host/Android.bp
index 7974462..cc8bc45 100644
--- a/tests/SystemMemoryTest/host/Android.bp
+++ b/tests/SystemMemoryTest/host/Android.bp
@@ -26,4 +26,7 @@
srcs: ["src/**/*.java"],
libs: ["tradefed"],
test_suites: ["general-tests"],
+ data: [
+ ":SystemMemoryTestDevice",
+ ],
}