diff options
758 files changed, 10659 insertions, 4522 deletions
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp index c967e51e16f9..45c6b8c5bf16 100644 --- a/apct-tests/perftests/multiuser/Android.bp +++ b/apct-tests/perftests/multiuser/Android.bp @@ -31,6 +31,9 @@ android_test { ], 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 81cec9111b88..e84aea1e7fac 100644 --- a/apct-tests/perftests/packagemanager/Android.bp +++ b/apct-tests/perftests/packagemanager/Android.bp @@ -33,7 +33,10 @@ android_test { 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 3fc87d394574..ce381b6699ea 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.ActivityManagerInternal.AppBackgroundRestrictionListener; 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 @@ public class AppStateTrackerImpl implements AppStateTracker { 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 class AppStateTrackerImpl implements AppStateTracker { 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 70b06cb534e2..887ee5fe1583 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 8355e9c6da99..44700c86efef 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 @@ public final class JobServiceContext implements ServiceConnection { 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 @@ public final class JobServiceContext implements ServiceConnection { 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 5d2c9261c537..b9e3b76b0279 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 @@ public final class FlexibilityController extends StateController { 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 7cc2f28a5664..6445c3bb6f8d 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 @@ public final class JobStatus { 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 aca0a6e9b18c..175c8d1cc4f3 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.Nullable; 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 @@ public final class QuotaController extends StateController { } }; - 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 3578c8acbd0e..585366755191 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.IntDef; 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 @@ class ProcessStateModifier extends Modifier { @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 @@ class ProcessStateModifier extends Modifier { 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 b6315862c634..b1f779f9c2cc 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -427,7 +427,7 @@ package android.app { 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 29f774cc39d7..a6313dbf52df 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -46,6 +46,7 @@ import android.os.ServiceManager; 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 @@ public class StatusBarManager { */ @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 12882a2f47c9..9efdf2831b9e 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -39,21 +39,22 @@ import android.hardware.input.VirtualNavigationTouchpadConfig; 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 @@ interface IVirtualDevice { 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 @@ interface IVirtualDevice { /** * 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 @@ interface IVirtualDevice { * 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 4f49b8dbd0dc..07743cef5889 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -101,7 +101,7 @@ interface IVirtualDeviceManager { * * @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 91c209fa098e..f28455477c50 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl @@ -28,7 +28,7 @@ oneway interface IVirtualDeviceSoundEffectListener { * 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 4a09186570e0..4ee65e077382 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -26,6 +26,11 @@ import java.util.Objects; /** * 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 6b851a11ad50..8b6377abc312 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -68,7 +68,13 @@ import java.util.concurrent.Executor; 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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { * * @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 @@ public final class VirtualDeviceManager { * * @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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { } /** - * 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 @@ public final class VirtualDeviceManager { } /** - * @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 @@ public final class VirtualDeviceManager { * @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 @@ public final class VirtualDeviceManager { * * @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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { /** * 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 @@ public final class VirtualDeviceManager { * * @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 @@ public final class VirtualDeviceManager { * 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 @@ public final class VirtualDeviceManager { } /** - * 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 @@ public final class VirtualDeviceManager { } /** - * 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 @@ public final class VirtualDeviceManager { } /** - * 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 @@ public final class VirtualDeviceManager { * {@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 @@ public final class VirtualDeviceManager { /** * Listener for system sound effect playback on virtual device. + * * @hide */ @SystemApi @@ -807,8 +825,8 @@ public final class VirtualDeviceManager { /** * 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 9a34dbe2699c..45d6dc62bfe8 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.VirtualSensorCallback; 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 @@ public final class VirtualDeviceParams implements Parcelable { * {@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 @@ public final class VirtualDeviceParams implements Parcelable { * <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 @@ public final class VirtualDeviceParams implements Parcelable { * <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 d6d0d2b79c83..dd5e660b998e 100644 --- a/core/java/android/companion/virtual/audio/AudioCapture.java +++ b/core/java/android/companion/virtual/audio/AudioCapture.java @@ -56,12 +56,12 @@ public final class AudioCapture { /** * 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 9d6a3eb84351..5de5f7ef4779 100644 --- a/core/java/android/companion/virtual/audio/AudioInjection.java +++ b/core/java/android/companion/virtual/audio/AudioInjection.java @@ -65,12 +65,12 @@ public final class AudioInjection { /** * 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 3cb0572f3350..dcdb6c6b5f7e 100644 --- a/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl +++ b/core/java/android/companion/virtual/sensor/IVirtualSensorCallback.aidl @@ -33,7 +33,7 @@ oneway interface IVirtualSensorCallback { * @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 @@ oneway interface IVirtualSensorCallback { * @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 bda44d402823..eaa17925b14b 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensor.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java @@ -30,7 +30,7 @@ import android.os.RemoteException; * 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 e6bd6daa060f..4d586f681b49 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorCallback.java @@ -45,10 +45,10 @@ public interface VirtualSensorCallback { * * @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 ef55ca97585d..3bdf9aa8015b 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -31,7 +31,9 @@ import java.util.Objects; /** * Configuration for creation of a virtual sensor. + * * @see VirtualSensor + * * @hide */ @SystemApi @@ -122,6 +124,7 @@ public final class VirtualSensorConfig implements Parcelable { /** * Returns the vendor string of the sensor. + * * @see Builder#setVendor */ @Nullable @@ -130,7 +133,8 @@ public final class VirtualSensorConfig implements Parcelable { } /** - * 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 @@ public final class VirtualSensorConfig implements Parcelable { } /** - * 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 @@ public final class VirtualSensorConfig implements Parcelable { } /** - * 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 @@ public final class VirtualSensorConfig implements Parcelable { } /** - * 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 @@ public final class VirtualSensorConfig implements Parcelable { } /** - * 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 @@ public final class VirtualSensorConfig implements Parcelable { /** * Returns the sensor flags. + * * @hide */ public int getFlags() { @@ -233,7 +242,7 @@ public final class VirtualSensorConfig implements Parcelable { * * @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 @@ public final class VirtualSensorConfig implements Parcelable { /** * Sets the maximum range of the sensor in the sensor's unit. + * * @see Sensor#getMaximumRange */ @NonNull @@ -285,6 +295,7 @@ public final class VirtualSensorConfig implements Parcelable { /** * Sets the resolution of the sensor in the sensor's unit. + * * @see Sensor#getResolution */ @NonNull @@ -295,6 +306,7 @@ public final class VirtualSensorConfig implements Parcelable { /** * Sets the power in mA used by this sensor while in use. + * * @see Sensor#getPower */ @NonNull @@ -305,6 +317,7 @@ public final class VirtualSensorConfig implements Parcelable { /** * Sets the minimum delay allowed between two events in microseconds. + * * @see Sensor#getMinDelay */ @NonNull @@ -315,6 +328,7 @@ public final class VirtualSensorConfig implements Parcelable { /** * Sets the maximum delay between two sensor events in microseconds. + * * @see Sensor#getMaxDelay */ @NonNull @@ -339,11 +353,11 @@ public final class VirtualSensorConfig implements Parcelable { * 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 d352f94ffd76..f10e9d087a47 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelCallback.java @@ -45,6 +45,8 @@ import android.os.SharedMemory; * <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 @@ public interface VirtualSensorDirectChannelCallback { * @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 6aed96ff593e..bf78dd09e7c2 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java @@ -41,6 +41,41 @@ import java.util.concurrent.atomic.AtomicLong; * 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 01b49750572d..a368467ee8f2 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java @@ -121,7 +121,7 @@ public final class VirtualSensorEvent implements Parcelable { * 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 6ff42711ef1a..f946754bd9a1 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -531,8 +531,9 @@ public class IntentFilter implements Parcelable { 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 6bd9de481a79..d4c249e58c8a 100644 --- a/core/java/android/credentials/ui/CancelUiRequest.java +++ b/core/java/android/credentials/ui/CancelUiRequest.java @@ -40,24 +40,50 @@ public final class CancelUiRequest implements Parcelable { @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 dcfef56f86a4..5e8372d68eb2 100644 --- a/core/java/android/credentials/ui/IntentFactory.java +++ b/core/java/android/credentials/ui/IntentFactory.java @@ -72,7 +72,8 @@ public class IntentFactory { * @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 @@ public class IntentFactory { 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 140ef39f51c2..a5acf547b97b 100644 --- a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java +++ b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java @@ -179,7 +179,7 @@ public class RemoteLockscreenValidationClientImpl implements RemoteLockscreenVal 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 000000000000..439a0c2391c7 --- /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 547608899259..baefd853876b 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1218,47 +1218,51 @@ public final class Display { } /** - * 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); - } - } - int[] enabledTypes = new int[enabledTypesSet.size()]; - int index = 0; - for (int enabledType : enabledTypesSet) { - enabledTypes[index++] = enabledType; + supportedHdrTypes = new int[enabledTypesSet.size()]; + int index = 0; + for (int enabledType : enabledTypesSet) { + supportedHdrTypes[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 24a0355dd10e..d35aff9a72b7 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -158,6 +158,14 @@ public final class InputWindowHandle { */ 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 @@ public final class InputWindowHandle { 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 0db52aaa8b3d..bc6a3b540ce7 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -265,7 +265,7 @@ public final class SurfaceControl implements Parcelable { 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 final class SurfaceControl implements Parcelable { */ 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 0560cafe3e52..98681446446b 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -57,18 +57,16 @@ public class WindowlessWindowManager implements IWindowSession { 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 @@ public class WindowlessWindowManager implements IWindowSession { .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 687253683dce..1840bcb358a0 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1217,9 +1217,11 @@ public interface InputConnection { * 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 @@ public interface InputConnection { * {@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 515b95cd951d..82cf07355a56 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1648,6 +1648,7 @@ public final class InputMethodManager { * * @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 @@ public final class InputMethodManager { } /** + * 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 4bd15f27a91a..4cc7ec598dbf 100644 --- a/core/java/android/window/RemoteTransition.java +++ b/core/java/android/window/RemoteTransition.java @@ -38,9 +38,18 @@ public class RemoteTransition implements Parcelable { /** 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 @@ public class RemoteTransition implements Parcelable { * 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 @@ public class RemoteTransition implements Parcelable { } /** + * 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 @@ public class RemoteTransition implements Parcelable { 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 @@ public class RemoteTransition implements Parcelable { return "RemoteTransition { " + "remoteTransition = " + mRemoteTransition + ", " + - "appThread = " + mAppThread + + "appThread = " + mAppThread + ", " + + "debugName = " + mDebugName + " }"; } @@ -139,9 +170,11 @@ public class RemoteTransition implements Parcelable { 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 @@ public class RemoteTransition implements Parcelable { 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 @@ public class RemoteTransition implements Parcelable { }; @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 4c482460543a..0f3eef7a3289 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -149,8 +149,11 @@ public final class TransitionInfo implements Parcelable { /** 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 @@ public final class TransitionInfo implements Parcelable { 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 @@ public final class TransitionInfo implements Parcelable { 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 @@ public final class TransitionInfo implements Parcelable { in.readTypedList(mChanges, Change.CREATOR); in.readTypedList(mRoots, Root.CREATOR); mOptions = in.readTypedObject(AnimationOptions.CREATOR); + mDebugId = in.readInt(); } @Override @@ -212,6 +220,7 @@ public final class TransitionInfo implements Parcelable { dest.writeTypedList(mChanges); dest.writeTypedList(mRoots, flags); dest.writeTypedObject(mOptions, flags); + dest.writeInt(mDebugId); } @NonNull @@ -347,11 +356,24 @@ public final class TransitionInfo implements Parcelable { 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 final class TransitionInfo implements Parcelable { */ 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 ad20432bd57d..9f804b1e1a7f 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -1586,61 +1586,109 @@ public final class WindowContainerTransaction implements Parcelable { 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 0cb87fef09a1..7ad2a6898fb7 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 final class SystemUiDeviceConfigFlags { 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 6344568480b7..4b9e77e83166 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_IN 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 class InteractionJankMonitor { 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 @@ public class InteractionJankMonitor { 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 @@ public class InteractionJankMonitor { 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 @@ public class InteractionJankMonitor { 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 f7c03cd42a99..ae58626e49eb 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.INearbyMediaDevicesProvider; 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 @@ oneway interface IStatusBar 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 c1dbc87a2a10..370885936211 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.media.MediaRoute2Info; 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 @@ interface IStatusBarService 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 241320f31748..416d991bd64c 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -74,6 +74,7 @@ static struct { WeakRefHandleField touchableRegionSurfaceControl; jfieldID transform; jfieldID windowToken; + jfieldID focusTransferTarget; } gInputWindowHandleClassInfo; static struct { @@ -216,6 +217,17 @@ bool NativeInputWindowHandle::updateInfo() { 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 @@ int register_android_view_InputWindowHandle(JNIEnv* env) { 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 03d6eece61e6..e42c6f107e6d 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1820,17 +1820,11 @@ static void nativeRemoveCurrentInputFocus(JNIEnv* env, jclass clazz, jlong trans } 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 @@ static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionO 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 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (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 e62af74fd439..bab4b6efd49d 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -21,6 +21,7 @@ option java_multiple_files = true; 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 @@ message SettingsServiceDumpProto { // 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 000000000000..9dcbad2c979f --- /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 e6c8557a8c50..bb3089bb397a 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -391,6 +391,9 @@ message ActivityRecordProto { 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 Binary files differnew file mode 100644 index 000000000000..d33fe3caf7b6 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_alias.png diff --git a/core/res/res/drawable-hdpi/pointer_all_scroll.png b/core/res/res/drawable-hdpi/pointer_all_scroll.png Binary files differnew file mode 100644 index 000000000000..095aadce579d --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_all_scroll.png diff --git a/core/res/res/drawable-hdpi/pointer_arrow.png b/core/res/res/drawable-hdpi/pointer_arrow.png Binary files differindex 85d066e77d36..a949a1a6a923 100644 --- a/core/res/res/drawable-hdpi/pointer_arrow.png +++ b/core/res/res/drawable-hdpi/pointer_arrow.png diff --git a/core/res/res/drawable-hdpi/pointer_cell.png b/core/res/res/drawable-hdpi/pointer_cell.png Binary files differnew file mode 100644 index 000000000000..76910e63b8ff --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_cell.png diff --git a/core/res/res/drawable-hdpi/pointer_context_menu.png b/core/res/res/drawable-hdpi/pointer_context_menu.png Binary files differnew file mode 100644 index 000000000000..c45d29baed21 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_context_menu.png diff --git a/core/res/res/drawable-hdpi/pointer_copy.png b/core/res/res/drawable-hdpi/pointer_copy.png Binary files differnew file mode 100644 index 000000000000..c5eda2e5b5c4 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_copy.png diff --git a/core/res/res/drawable-hdpi/pointer_crosshair.png b/core/res/res/drawable-hdpi/pointer_crosshair.png Binary files differnew file mode 100644 index 000000000000..be767b286f35 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_crosshair.png diff --git a/core/res/res/drawable-hdpi/pointer_grab.png b/core/res/res/drawable-hdpi/pointer_grab.png Binary files differnew file mode 100644 index 000000000000..26da04de1d12 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_grab.png diff --git a/core/res/res/drawable-hdpi/pointer_grabbing.png b/core/res/res/drawable-hdpi/pointer_grabbing.png Binary files differnew file mode 100644 index 000000000000..f4031a9227c7 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-hdpi/pointer_hand.png b/core/res/res/drawable-hdpi/pointer_hand.png Binary files differnew file mode 100644 index 000000000000..a7ae55fc4b5f --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_hand.png diff --git a/core/res/res/drawable-hdpi/pointer_help.png b/core/res/res/drawable-hdpi/pointer_help.png Binary files differnew file mode 100644 index 000000000000..a3afdb695897 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_help.png diff --git a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..9388f162b17e --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png diff --git a/core/res/res/drawable-hdpi/pointer_nodrop.png b/core/res/res/drawable-hdpi/pointer_nodrop.png Binary files differnew file mode 100644 index 000000000000..7043323701dd --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-hdpi/pointer_spot_anchor.png b/core/res/res/drawable-hdpi/pointer_spot_anchor.png Binary files differindex 784f613de936..4b74e2dd2363 100644 --- a/core/res/res/drawable-hdpi/pointer_spot_anchor.png +++ b/core/res/res/drawable-hdpi/pointer_spot_anchor.png diff --git a/core/res/res/drawable-hdpi/pointer_spot_hover.png b/core/res/res/drawable-hdpi/pointer_spot_hover.png Binary files differindex 0e8353c59e22..68d6e4a23831 100644 --- a/core/res/res/drawable-hdpi/pointer_spot_hover.png +++ b/core/res/res/drawable-hdpi/pointer_spot_hover.png diff --git a/core/res/res/drawable-hdpi/pointer_spot_touch.png b/core/res/res/drawable-hdpi/pointer_spot_touch.png Binary files differindex 3ad9b10dda0d..fda831fbc82f 100644 --- a/core/res/res/drawable-hdpi/pointer_spot_touch.png +++ b/core/res/res/drawable-hdpi/pointer_spot_touch.png diff --git a/core/res/res/drawable-hdpi/pointer_text.png b/core/res/res/drawable-hdpi/pointer_text.png Binary files differnew file mode 100644 index 000000000000..ab0c80a1714f --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_text.png 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 Binary files differnew file mode 100644 index 000000000000..ab52bffd9de5 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png 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 Binary files differnew file mode 100644 index 000000000000..1250d35df469 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png diff --git a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png Binary files differnew file mode 100644 index 000000000000..6730c7b4a365 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png diff --git a/core/res/res/drawable-hdpi/pointer_vertical_text.png b/core/res/res/drawable-hdpi/pointer_vertical_text.png Binary files differnew file mode 100644 index 000000000000..f079bc1a5f4a --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_vertical_text.png diff --git a/core/res/res/drawable-hdpi/pointer_zoom_in.png b/core/res/res/drawable-hdpi/pointer_zoom_in.png Binary files differnew file mode 100644 index 000000000000..a3dc84baef3c --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_zoom_in.png diff --git a/core/res/res/drawable-hdpi/pointer_zoom_out.png b/core/res/res/drawable-hdpi/pointer_zoom_out.png Binary files differnew file mode 100644 index 000000000000..3ee31bd66b80 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_zoom_out.png diff --git a/core/res/res/drawable-mdpi/pointer_alias.png b/core/res/res/drawable-mdpi/pointer_alias.png Binary files differindex 8f61a39dd209..619567a88e33 100644 --- a/core/res/res/drawable-mdpi/pointer_alias.png +++ b/core/res/res/drawable-mdpi/pointer_alias.png diff --git a/core/res/res/drawable-mdpi/pointer_alias_large.png b/core/res/res/drawable-mdpi/pointer_alias_large.png Binary files differindex 606774d4cb73..b3f382e56bfb 100644 --- a/core/res/res/drawable-mdpi/pointer_alias_large.png +++ b/core/res/res/drawable-mdpi/pointer_alias_large.png diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png Binary files differindex a897ef4799a4..3db456e885d2 100644 --- a/core/res/res/drawable-mdpi/pointer_all_scroll.png +++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png Binary files differindex c29db8702736..120e1d72d233 100644 --- a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png +++ b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png diff --git a/core/res/res/drawable-mdpi/pointer_arrow.png b/core/res/res/drawable-mdpi/pointer_arrow.png Binary files differindex 7a74ec113365..77e4354eb3f5 100644 --- a/core/res/res/drawable-mdpi/pointer_arrow.png +++ b/core/res/res/drawable-mdpi/pointer_arrow.png diff --git a/core/res/res/drawable-mdpi/pointer_arrow_large.png b/core/res/res/drawable-mdpi/pointer_arrow_large.png Binary files differindex 9f59c4cae921..e28a7a559aed 100644 --- a/core/res/res/drawable-mdpi/pointer_arrow_large.png +++ b/core/res/res/drawable-mdpi/pointer_arrow_large.png diff --git a/core/res/res/drawable-mdpi/pointer_cell.png b/core/res/res/drawable-mdpi/pointer_cell.png Binary files differindex b5213890f0ac..e5ce9461c6e8 100644 --- a/core/res/res/drawable-mdpi/pointer_cell.png +++ b/core/res/res/drawable-mdpi/pointer_cell.png diff --git a/core/res/res/drawable-mdpi/pointer_cell_large.png b/core/res/res/drawable-mdpi/pointer_cell_large.png Binary files differindex 3dec5e5d825f..fcb9fc89a768 100644 --- a/core/res/res/drawable-mdpi/pointer_cell_large.png +++ b/core/res/res/drawable-mdpi/pointer_cell_large.png diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png Binary files differindex 4e1ba4ef3fbe..e0e849d0bff7 100644 --- a/core/res/res/drawable-mdpi/pointer_context_menu.png +++ b/core/res/res/drawable-mdpi/pointer_context_menu.png diff --git a/core/res/res/drawable-mdpi/pointer_context_menu_large.png b/core/res/res/drawable-mdpi/pointer_context_menu_large.png Binary files differindex 7c9e250d4884..e8c9be4bfa57 100644 --- a/core/res/res/drawable-mdpi/pointer_context_menu_large.png +++ b/core/res/res/drawable-mdpi/pointer_context_menu_large.png diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png Binary files differindex 254485cebd81..e731108370d3 100644 --- a/core/res/res/drawable-mdpi/pointer_copy.png +++ b/core/res/res/drawable-mdpi/pointer_copy.png diff --git a/core/res/res/drawable-mdpi/pointer_copy_large.png b/core/res/res/drawable-mdpi/pointer_copy_large.png Binary files differindex 2f0e082b0927..15ccb04b8f76 100644 --- a/core/res/res/drawable-mdpi/pointer_copy_large.png +++ b/core/res/res/drawable-mdpi/pointer_copy_large.png diff --git a/core/res/res/drawable-mdpi/pointer_crosshair.png b/core/res/res/drawable-mdpi/pointer_crosshair.png Binary files differindex 8a06c773de7e..be6bd342a1c7 100644 --- a/core/res/res/drawable-mdpi/pointer_crosshair.png +++ b/core/res/res/drawable-mdpi/pointer_crosshair.png diff --git a/core/res/res/drawable-mdpi/pointer_crosshair_large.png b/core/res/res/drawable-mdpi/pointer_crosshair_large.png Binary files differindex 51faf96cafd1..657df8d24ccf 100644 --- a/core/res/res/drawable-mdpi/pointer_crosshair_large.png +++ b/core/res/res/drawable-mdpi/pointer_crosshair_large.png diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png Binary files differindex 0e0ea2eff072..d625b55f7066 100644 --- a/core/res/res/drawable-mdpi/pointer_grab.png +++ b/core/res/res/drawable-mdpi/pointer_grab.png diff --git a/core/res/res/drawable-mdpi/pointer_grab_large.png b/core/res/res/drawable-mdpi/pointer_grab_large.png Binary files differindex 44a171c7c424..9d36df0d6d94 100644 --- a/core/res/res/drawable-mdpi/pointer_grab_large.png +++ b/core/res/res/drawable-mdpi/pointer_grab_large.png diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png Binary files differindex 9deb64cb108d..71bb17ba5592 100644 --- a/core/res/res/drawable-mdpi/pointer_grabbing.png +++ b/core/res/res/drawable-mdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-mdpi/pointer_grabbing_large.png b/core/res/res/drawable-mdpi/pointer_grabbing_large.png Binary files differindex b602d2f78ded..5574b07faf44 100644 --- a/core/res/res/drawable-mdpi/pointer_grabbing_large.png +++ b/core/res/res/drawable-mdpi/pointer_grabbing_large.png diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png Binary files differindex c614d9ebce7f..d7f7beda111c 100644 --- a/core/res/res/drawable-mdpi/pointer_hand.png +++ b/core/res/res/drawable-mdpi/pointer_hand.png diff --git a/core/res/res/drawable-mdpi/pointer_hand_large.png b/core/res/res/drawable-mdpi/pointer_hand_large.png Binary files differindex 464ec28e4161..f775464ced18 100644 --- a/core/res/res/drawable-mdpi/pointer_hand_large.png +++ b/core/res/res/drawable-mdpi/pointer_hand_large.png diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png Binary files differindex d54b2b109bbd..286242cfc514 100644 --- a/core/res/res/drawable-mdpi/pointer_help.png +++ b/core/res/res/drawable-mdpi/pointer_help.png diff --git a/core/res/res/drawable-mdpi/pointer_help_large.png b/core/res/res/drawable-mdpi/pointer_help_large.png Binary files differindex 69d1e419bda9..27f4a845e06c 100644 --- a/core/res/res/drawable-mdpi/pointer_help_large.png +++ b/core/res/res/drawable-mdpi/pointer_help_large.png diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png Binary files differindex a2951a9ef077..20f319ac5cc4 100644 --- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png +++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png 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 Binary files differindex 70861065d4ef..33ef5c96ac8a 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 diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png Binary files differindex aa928953cf0a..931b74094d79 100644 --- a/core/res/res/drawable-mdpi/pointer_nodrop.png +++ b/core/res/res/drawable-mdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-mdpi/pointer_nodrop_large.png b/core/res/res/drawable-mdpi/pointer_nodrop_large.png Binary files differindex e150d042a7bc..88f77d300a15 100644 --- a/core/res/res/drawable-mdpi/pointer_nodrop_large.png +++ b/core/res/res/drawable-mdpi/pointer_nodrop_large.png diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png Binary files differindex 48d638b0479d..a8bc03b46243 100644 --- a/core/res/res/drawable-mdpi/pointer_spot_anchor.png +++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png Binary files differindex b30481592e65..c3672b22dfc6 100644 --- a/core/res/res/drawable-mdpi/pointer_spot_hover.png +++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png Binary files differindex 659f80955b00..1f146d2e27bb 100644 --- a/core/res/res/drawable-mdpi/pointer_spot_touch.png +++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png diff --git a/core/res/res/drawable-mdpi/pointer_text.png b/core/res/res/drawable-mdpi/pointer_text.png Binary files differindex 34d1698c6ff1..68a5535b1544 100644 --- a/core/res/res/drawable-mdpi/pointer_text.png +++ b/core/res/res/drawable-mdpi/pointer_text.png diff --git a/core/res/res/drawable-mdpi/pointer_text_large.png b/core/res/res/drawable-mdpi/pointer_text_large.png Binary files differindex 2fba190552b9..599dc6968da7 100644 --- a/core/res/res/drawable-mdpi/pointer_text_large.png +++ b/core/res/res/drawable-mdpi/pointer_text_large.png 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 Binary files differindex b0cd92ced9a9..fe7d49602aa5 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 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 Binary files differindex eecaa89204ed..7b2e20c9d19c 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 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 Binary files differindex f8d3527fcd0a..95a662017927 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 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 Binary files differindex 9d47ecf793ef..2e2904b6562d 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 diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png Binary files differindex 48c93798714e..ae6bfed37812 100644 --- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png +++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png 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 Binary files differindex fd777b14bad1..3beb1d1e9c8c 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 diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text.png b/core/res/res/drawable-mdpi/pointer_vertical_text.png Binary files differindex 9fcbcba80867..06a536b27861 100644 --- a/core/res/res/drawable-mdpi/pointer_vertical_text.png +++ b/core/res/res/drawable-mdpi/pointer_vertical_text.png diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text_large.png b/core/res/res/drawable-mdpi/pointer_vertical_text_large.png Binary files differindex 1cbe49adb32a..f03179ba24b4 100644 --- a/core/res/res/drawable-mdpi/pointer_vertical_text_large.png +++ b/core/res/res/drawable-mdpi/pointer_vertical_text_large.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_0.png b/core/res/res/drawable-mdpi/pointer_wait_0.png Binary files differindex ae32a4417f08..aefdb467165a 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_0.png +++ b/core/res/res/drawable-mdpi/pointer_wait_0.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_1.png b/core/res/res/drawable-mdpi/pointer_wait_1.png Binary files differindex afadc3153dbc..19396602031b 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_1.png +++ b/core/res/res/drawable-mdpi/pointer_wait_1.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_10.png b/core/res/res/drawable-mdpi/pointer_wait_10.png Binary files differindex 4e5f3b090eea..cd3a8f542c3b 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_10.png +++ b/core/res/res/drawable-mdpi/pointer_wait_10.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_11.png b/core/res/res/drawable-mdpi/pointer_wait_11.png Binary files differindex f895e5344368..e8894ede7194 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_11.png +++ b/core/res/res/drawable-mdpi/pointer_wait_11.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_12.png b/core/res/res/drawable-mdpi/pointer_wait_12.png Binary files differindex 7a155f53be3c..f5af8b03f328 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_12.png +++ b/core/res/res/drawable-mdpi/pointer_wait_12.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_13.png b/core/res/res/drawable-mdpi/pointer_wait_13.png Binary files differindex a9ae639ae2af..06e4462434aa 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_13.png +++ b/core/res/res/drawable-mdpi/pointer_wait_13.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_14.png b/core/res/res/drawable-mdpi/pointer_wait_14.png Binary files differindex 6761dda86397..7f0ac45598c8 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_14.png +++ b/core/res/res/drawable-mdpi/pointer_wait_14.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_15.png b/core/res/res/drawable-mdpi/pointer_wait_15.png Binary files differindex 98821ed064aa..577fb78d7f87 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_15.png +++ b/core/res/res/drawable-mdpi/pointer_wait_15.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_16.png b/core/res/res/drawable-mdpi/pointer_wait_16.png Binary files differindex 72f385313f0d..d111d0b3a19f 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_16.png +++ b/core/res/res/drawable-mdpi/pointer_wait_16.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_17.png b/core/res/res/drawable-mdpi/pointer_wait_17.png Binary files differindex a7452edfe132..55bd35a0da88 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_17.png +++ b/core/res/res/drawable-mdpi/pointer_wait_17.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_18.png b/core/res/res/drawable-mdpi/pointer_wait_18.png Binary files differindex ecb4f723f9b1..a1870cadea25 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_18.png +++ b/core/res/res/drawable-mdpi/pointer_wait_18.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_19.png b/core/res/res/drawable-mdpi/pointer_wait_19.png Binary files differindex 1ce5d703670c..ffc44351ba7c 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_19.png +++ b/core/res/res/drawable-mdpi/pointer_wait_19.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_2.png b/core/res/res/drawable-mdpi/pointer_wait_2.png Binary files differindex d42278a02d92..04314b7263c0 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_2.png +++ b/core/res/res/drawable-mdpi/pointer_wait_2.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_20.png b/core/res/res/drawable-mdpi/pointer_wait_20.png Binary files differindex 2736fea61871..c98ababfc917 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_20.png +++ b/core/res/res/drawable-mdpi/pointer_wait_20.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_21.png b/core/res/res/drawable-mdpi/pointer_wait_21.png Binary files differindex e2fafd1fa215..778e829a2a91 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_21.png +++ b/core/res/res/drawable-mdpi/pointer_wait_21.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_22.png b/core/res/res/drawable-mdpi/pointer_wait_22.png Binary files differindex 24bd01a313b3..9d6175642f9d 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_22.png +++ b/core/res/res/drawable-mdpi/pointer_wait_22.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_23.png b/core/res/res/drawable-mdpi/pointer_wait_23.png Binary files differindex 26c6129a7412..68c1def01170 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_23.png +++ b/core/res/res/drawable-mdpi/pointer_wait_23.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_24.png b/core/res/res/drawable-mdpi/pointer_wait_24.png Binary files differindex 25979633b9f4..cb4e59f15bfd 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_24.png +++ b/core/res/res/drawable-mdpi/pointer_wait_24.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_25.png b/core/res/res/drawable-mdpi/pointer_wait_25.png Binary files differindex c925d821eccc..64662b2be0fa 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_25.png +++ b/core/res/res/drawable-mdpi/pointer_wait_25.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_26.png b/core/res/res/drawable-mdpi/pointer_wait_26.png Binary files differindex 7c3735db540a..8a4a7304f87a 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_26.png +++ b/core/res/res/drawable-mdpi/pointer_wait_26.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_27.png b/core/res/res/drawable-mdpi/pointer_wait_27.png Binary files differindex d4f2f65a3e0d..65782256ec65 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_27.png +++ b/core/res/res/drawable-mdpi/pointer_wait_27.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_28.png b/core/res/res/drawable-mdpi/pointer_wait_28.png Binary files differindex 582c276fb24c..233efc3d2ffe 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_28.png +++ b/core/res/res/drawable-mdpi/pointer_wait_28.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_29.png b/core/res/res/drawable-mdpi/pointer_wait_29.png Binary files differindex f79f7156e8a8..2513064a1d16 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_29.png +++ b/core/res/res/drawable-mdpi/pointer_wait_29.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_3.png b/core/res/res/drawable-mdpi/pointer_wait_3.png Binary files differindex efc766e7b625..a4b3de52d96d 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_3.png +++ b/core/res/res/drawable-mdpi/pointer_wait_3.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_30.png b/core/res/res/drawable-mdpi/pointer_wait_30.png Binary files differindex 636d793e2f3d..f3dcdbb58533 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_30.png +++ b/core/res/res/drawable-mdpi/pointer_wait_30.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_31.png b/core/res/res/drawable-mdpi/pointer_wait_31.png Binary files differindex 8f41a53a53f7..c0709aa7509f 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_31.png +++ b/core/res/res/drawable-mdpi/pointer_wait_31.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_32.png b/core/res/res/drawable-mdpi/pointer_wait_32.png Binary files differindex deef9b7ad981..2456313ffd02 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_32.png +++ b/core/res/res/drawable-mdpi/pointer_wait_32.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_33.png b/core/res/res/drawable-mdpi/pointer_wait_33.png Binary files differindex 6cad76b41e1f..d5506a8f2b2e 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_33.png +++ b/core/res/res/drawable-mdpi/pointer_wait_33.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_34.png b/core/res/res/drawable-mdpi/pointer_wait_34.png Binary files differindex 4b258254d2fd..365213de9787 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_34.png +++ b/core/res/res/drawable-mdpi/pointer_wait_34.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_35.png b/core/res/res/drawable-mdpi/pointer_wait_35.png Binary files differindex ccfaf74dacf1..577c7b6ef7ee 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_35.png +++ b/core/res/res/drawable-mdpi/pointer_wait_35.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_36.png b/core/res/res/drawable-mdpi/pointer_wait_36.png Binary files differnew file mode 100644 index 000000000000..9f7202b146c5 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_36.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_37.png b/core/res/res/drawable-mdpi/pointer_wait_37.png Binary files differnew file mode 100644 index 000000000000..7a041af928c1 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_37.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_38.png b/core/res/res/drawable-mdpi/pointer_wait_38.png Binary files differnew file mode 100644 index 000000000000..d61545242937 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_38.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_39.png b/core/res/res/drawable-mdpi/pointer_wait_39.png Binary files differnew file mode 100644 index 000000000000..b6ecf0f0016c --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_39.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_4.png b/core/res/res/drawable-mdpi/pointer_wait_4.png Binary files differindex d39d13afe5ff..f2a7759797bb 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_4.png +++ b/core/res/res/drawable-mdpi/pointer_wait_4.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_40.png b/core/res/res/drawable-mdpi/pointer_wait_40.png Binary files differnew file mode 100644 index 000000000000..408ac15c3a0d --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_40.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_41.png b/core/res/res/drawable-mdpi/pointer_wait_41.png Binary files differnew file mode 100644 index 000000000000..54f38cf23b08 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_41.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_42.png b/core/res/res/drawable-mdpi/pointer_wait_42.png Binary files differnew file mode 100644 index 000000000000..7894cf5ef515 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_42.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_43.png b/core/res/res/drawable-mdpi/pointer_wait_43.png Binary files differnew file mode 100644 index 000000000000..8bd34afa81a2 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_43.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_44.png b/core/res/res/drawable-mdpi/pointer_wait_44.png Binary files differnew file mode 100644 index 000000000000..b79e17bf778b --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_44.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_45.png b/core/res/res/drawable-mdpi/pointer_wait_45.png Binary files differnew file mode 100644 index 000000000000..7105121ff0c5 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_45.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_46.png b/core/res/res/drawable-mdpi/pointer_wait_46.png Binary files differnew file mode 100644 index 000000000000..09b2f611e517 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_46.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_47.png b/core/res/res/drawable-mdpi/pointer_wait_47.png Binary files differnew file mode 100644 index 000000000000..2415548a41df --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_47.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_48.png b/core/res/res/drawable-mdpi/pointer_wait_48.png Binary files differnew file mode 100644 index 000000000000..7b51615c7d4f --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_48.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_49.png b/core/res/res/drawable-mdpi/pointer_wait_49.png Binary files differnew file mode 100644 index 000000000000..985c7bca1279 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_49.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_5.png b/core/res/res/drawable-mdpi/pointer_wait_5.png Binary files differindex 1c5a7de0980f..a6f99410a5ee 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_5.png +++ b/core/res/res/drawable-mdpi/pointer_wait_5.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_50.png b/core/res/res/drawable-mdpi/pointer_wait_50.png Binary files differnew file mode 100644 index 000000000000..c8b031faa0b7 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_50.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_51.png b/core/res/res/drawable-mdpi/pointer_wait_51.png Binary files differnew file mode 100644 index 000000000000..74e74c948090 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_51.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_52.png b/core/res/res/drawable-mdpi/pointer_wait_52.png Binary files differnew file mode 100644 index 000000000000..e42252d55a1d --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_52.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_53.png b/core/res/res/drawable-mdpi/pointer_wait_53.png Binary files differnew file mode 100644 index 000000000000..860de2a12a47 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_53.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_54.png b/core/res/res/drawable-mdpi/pointer_wait_54.png Binary files differnew file mode 100644 index 000000000000..eb47cc19e331 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_54.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_55.png b/core/res/res/drawable-mdpi/pointer_wait_55.png Binary files differnew file mode 100644 index 000000000000..ce853acc27fe --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_55.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_56.png b/core/res/res/drawable-mdpi/pointer_wait_56.png Binary files differnew file mode 100644 index 000000000000..e17ec114a10f --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_56.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_57.png b/core/res/res/drawable-mdpi/pointer_wait_57.png Binary files differnew file mode 100644 index 000000000000..f450f0451beb --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_57.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_58.png b/core/res/res/drawable-mdpi/pointer_wait_58.png Binary files differnew file mode 100644 index 000000000000..f3d26dd03a2a --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_58.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_59.png b/core/res/res/drawable-mdpi/pointer_wait_59.png Binary files differnew file mode 100644 index 000000000000..5220f3c4262e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_59.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_6.png b/core/res/res/drawable-mdpi/pointer_wait_6.png Binary files differindex 5113b272095f..5f2b1f9d201a 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_6.png +++ b/core/res/res/drawable-mdpi/pointer_wait_6.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_60.png b/core/res/res/drawable-mdpi/pointer_wait_60.png Binary files differnew file mode 100644 index 000000000000..b01a0cd08806 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_60.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_61.png b/core/res/res/drawable-mdpi/pointer_wait_61.png Binary files differnew file mode 100644 index 000000000000..07a132d89210 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_61.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_62.png b/core/res/res/drawable-mdpi/pointer_wait_62.png Binary files differnew file mode 100644 index 000000000000..52e97681ba80 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_62.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_63.png b/core/res/res/drawable-mdpi/pointer_wait_63.png Binary files differnew file mode 100644 index 000000000000..85b36f7bfcad --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_63.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_64.png b/core/res/res/drawable-mdpi/pointer_wait_64.png Binary files differnew file mode 100644 index 000000000000..d68475218f2c --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_64.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_65.png b/core/res/res/drawable-mdpi/pointer_wait_65.png Binary files differnew file mode 100644 index 000000000000..7c0ee307067e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_65.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_66.png b/core/res/res/drawable-mdpi/pointer_wait_66.png Binary files differnew file mode 100644 index 000000000000..54a720445404 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_66.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_67.png b/core/res/res/drawable-mdpi/pointer_wait_67.png Binary files differnew file mode 100644 index 000000000000..8416304d042a --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_67.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_68.png b/core/res/res/drawable-mdpi/pointer_wait_68.png Binary files differnew file mode 100644 index 000000000000..af2e5e26e0bc --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_68.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_69.png b/core/res/res/drawable-mdpi/pointer_wait_69.png Binary files differnew file mode 100644 index 000000000000..dd440d7d0c63 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_69.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_7.png b/core/res/res/drawable-mdpi/pointer_wait_7.png Binary files differindex 766a7169f4ff..d950c530ed89 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_7.png +++ b/core/res/res/drawable-mdpi/pointer_wait_7.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_70.png b/core/res/res/drawable-mdpi/pointer_wait_70.png Binary files differnew file mode 100644 index 000000000000..3b3843b8fd96 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_70.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_71.png b/core/res/res/drawable-mdpi/pointer_wait_71.png Binary files differnew file mode 100644 index 000000000000..e8aba4c20f9c --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_71.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_72.png b/core/res/res/drawable-mdpi/pointer_wait_72.png Binary files differnew file mode 100644 index 000000000000..17c30d50f2b5 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_72.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_73.png b/core/res/res/drawable-mdpi/pointer_wait_73.png Binary files differnew file mode 100644 index 000000000000..9336a80c12aa --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_73.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_74.png b/core/res/res/drawable-mdpi/pointer_wait_74.png Binary files differnew file mode 100644 index 000000000000..1f18ea56b792 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_74.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_75.png b/core/res/res/drawable-mdpi/pointer_wait_75.png Binary files differnew file mode 100644 index 000000000000..24d2b774dfac --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_75.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_76.png b/core/res/res/drawable-mdpi/pointer_wait_76.png Binary files differnew file mode 100644 index 000000000000..21cd4b1f2d44 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_76.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_77.png b/core/res/res/drawable-mdpi/pointer_wait_77.png Binary files differnew file mode 100644 index 000000000000..53d653247ac5 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_77.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_78.png b/core/res/res/drawable-mdpi/pointer_wait_78.png Binary files differnew file mode 100644 index 000000000000..769f1c81f876 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_78.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_79.png b/core/res/res/drawable-mdpi/pointer_wait_79.png Binary files differnew file mode 100644 index 000000000000..3f89a84f99b8 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_79.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_8.png b/core/res/res/drawable-mdpi/pointer_wait_8.png Binary files differindex 80fb2d9bf29e..c3026ea05fe9 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_8.png +++ b/core/res/res/drawable-mdpi/pointer_wait_8.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_80.png b/core/res/res/drawable-mdpi/pointer_wait_80.png Binary files differnew file mode 100644 index 000000000000..4cf0e15ddd0e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_wait_80.png diff --git a/core/res/res/drawable-mdpi/pointer_wait_9.png b/core/res/res/drawable-mdpi/pointer_wait_9.png Binary files differindex db07e87eabdf..f1fb13421a9e 100644 --- a/core/res/res/drawable-mdpi/pointer_wait_9.png +++ b/core/res/res/drawable-mdpi/pointer_wait_9.png diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in.png b/core/res/res/drawable-mdpi/pointer_zoom_in.png Binary files differindex 17c4e664f41c..ef4e4a5847f3 100644 --- a/core/res/res/drawable-mdpi/pointer_zoom_in.png +++ b/core/res/res/drawable-mdpi/pointer_zoom_in.png diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in_large.png b/core/res/res/drawable-mdpi/pointer_zoom_in_large.png Binary files differindex 9b0fa7f2a387..c484ce817139 100644 --- a/core/res/res/drawable-mdpi/pointer_zoom_in_large.png +++ b/core/res/res/drawable-mdpi/pointer_zoom_in_large.png diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out.png b/core/res/res/drawable-mdpi/pointer_zoom_out.png Binary files differindex 742f705960bb..af725a4f50fe 100644 --- a/core/res/res/drawable-mdpi/pointer_zoom_out.png +++ b/core/res/res/drawable-mdpi/pointer_zoom_out.png diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out_large.png b/core/res/res/drawable-mdpi/pointer_zoom_out_large.png Binary files differindex 1a9ec8654e82..448ea45c5678 100644 --- a/core/res/res/drawable-mdpi/pointer_zoom_out_large.png +++ b/core/res/res/drawable-mdpi/pointer_zoom_out_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_alias.png b/core/res/res/drawable-xhdpi/pointer_alias.png Binary files differindex fe0fd25d1446..d71ba71a9d36 100644 --- a/core/res/res/drawable-xhdpi/pointer_alias.png +++ b/core/res/res/drawable-xhdpi/pointer_alias.png diff --git a/core/res/res/drawable-xhdpi/pointer_alias_large.png b/core/res/res/drawable-xhdpi/pointer_alias_large.png Binary files differindex ad595ed6f374..b178901bdf67 100644 --- a/core/res/res/drawable-xhdpi/pointer_alias_large.png +++ b/core/res/res/drawable-xhdpi/pointer_alias_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png Binary files differindex e2374ece1067..e9d05d5079be 100644 --- a/core/res/res/drawable-xhdpi/pointer_all_scroll.png +++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png Binary files differindex 6029f52b2da3..1fd54fb3cc9b 100644 --- a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png +++ b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_arrow.png b/core/res/res/drawable-xhdpi/pointer_arrow.png Binary files differindex 1cfd03302916..b2608c524ec0 100644 --- a/core/res/res/drawable-xhdpi/pointer_arrow.png +++ b/core/res/res/drawable-xhdpi/pointer_arrow.png diff --git a/core/res/res/drawable-xhdpi/pointer_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_arrow_large.png Binary files differindex dc9007a998b1..6019c5ab9184 100644 --- a/core/res/res/drawable-xhdpi/pointer_arrow_large.png +++ b/core/res/res/drawable-xhdpi/pointer_arrow_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_cell.png b/core/res/res/drawable-xhdpi/pointer_cell.png Binary files differindex 4ca09e3229a2..e96b8bdc383e 100644 --- a/core/res/res/drawable-xhdpi/pointer_cell.png +++ b/core/res/res/drawable-xhdpi/pointer_cell.png diff --git a/core/res/res/drawable-xhdpi/pointer_cell_large.png b/core/res/res/drawable-xhdpi/pointer_cell_large.png Binary files differindex 50119b7f9975..01d02707f567 100644 --- a/core/res/res/drawable-xhdpi/pointer_cell_large.png +++ b/core/res/res/drawable-xhdpi/pointer_cell_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png Binary files differindex 05a59f8c9690..d4b2bdeb2cff 100644 --- a/core/res/res/drawable-xhdpi/pointer_context_menu.png +++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png Binary files differindex 148cf8729d7f..977df10c20d5 100644 --- a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png +++ b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png Binary files differindex 0cdbf21153e6..5b6cc5bc454f 100644 --- a/core/res/res/drawable-xhdpi/pointer_copy.png +++ b/core/res/res/drawable-xhdpi/pointer_copy.png diff --git a/core/res/res/drawable-xhdpi/pointer_copy_large.png b/core/res/res/drawable-xhdpi/pointer_copy_large.png Binary files differindex 0e1350c27c71..d78a410a2887 100644 --- a/core/res/res/drawable-xhdpi/pointer_copy_large.png +++ b/core/res/res/drawable-xhdpi/pointer_copy_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair.png b/core/res/res/drawable-xhdpi/pointer_crosshair.png Binary files differindex 86c649c1a1bc..d384c94fd6a8 100644 --- a/core/res/res/drawable-xhdpi/pointer_crosshair.png +++ b/core/res/res/drawable-xhdpi/pointer_crosshair.png diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair_large.png b/core/res/res/drawable-xhdpi/pointer_crosshair_large.png Binary files differindex fc59291fee75..165daf3fa1c5 100644 --- a/core/res/res/drawable-xhdpi/pointer_crosshair_large.png +++ b/core/res/res/drawable-xhdpi/pointer_crosshair_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png Binary files differindex b5c28ba15980..46dd3eeb95c6 100644 --- a/core/res/res/drawable-xhdpi/pointer_grab.png +++ b/core/res/res/drawable-xhdpi/pointer_grab.png diff --git a/core/res/res/drawable-xhdpi/pointer_grab_large.png b/core/res/res/drawable-xhdpi/pointer_grab_large.png Binary files differindex df98d8974fa4..1c7e63e2e527 100644 --- a/core/res/res/drawable-xhdpi/pointer_grab_large.png +++ b/core/res/res/drawable-xhdpi/pointer_grab_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png Binary files differindex 6aba589f9235..2fb8a9c4442f 100644 --- a/core/res/res/drawable-xhdpi/pointer_grabbing.png +++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png Binary files differindex f2d043c3b98b..3467a03e2a56 100644 --- a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png +++ b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png Binary files differindex 486cb24f0965..926310cce74d 100644 --- a/core/res/res/drawable-xhdpi/pointer_hand.png +++ b/core/res/res/drawable-xhdpi/pointer_hand.png diff --git a/core/res/res/drawable-xhdpi/pointer_hand_large.png b/core/res/res/drawable-xhdpi/pointer_hand_large.png Binary files differindex 31135893c4f9..546b222164b5 100644 --- a/core/res/res/drawable-xhdpi/pointer_hand_large.png +++ b/core/res/res/drawable-xhdpi/pointer_hand_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png Binary files differindex abcf92312e2d..5a6805c47772 100644 --- a/core/res/res/drawable-xhdpi/pointer_help.png +++ b/core/res/res/drawable-xhdpi/pointer_help.png diff --git a/core/res/res/drawable-xhdpi/pointer_help_large.png b/core/res/res/drawable-xhdpi/pointer_help_large.png Binary files differindex b745e1eb3f8f..4bdc3d14d5a3 100644 --- a/core/res/res/drawable-xhdpi/pointer_help_large.png +++ b/core/res/res/drawable-xhdpi/pointer_help_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png Binary files differindex 299ae1198b7f..caf2a97bb7be 100644 --- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png +++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png 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 Binary files differindex 9c8fa5cfa186..2f22640f99e6 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 diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png Binary files differindex 8e93f33064ab..fdfc2671255c 100644 --- a/core/res/res/drawable-xhdpi/pointer_nodrop.png +++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png Binary files differindex a392da7b5b40..2b5e8a4b0f59 100644 --- a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png +++ b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_spot_anchor.png b/core/res/res/drawable-xhdpi/pointer_spot_anchor.png Binary files differindex a1dcc146ae0d..c7407551bd27 100644 --- a/core/res/res/drawable-xhdpi/pointer_spot_anchor.png +++ b/core/res/res/drawable-xhdpi/pointer_spot_anchor.png diff --git a/core/res/res/drawable-xhdpi/pointer_spot_hover.png b/core/res/res/drawable-xhdpi/pointer_spot_hover.png Binary files differindex 668f841de1da..e734427ba56b 100644 --- a/core/res/res/drawable-xhdpi/pointer_spot_hover.png +++ b/core/res/res/drawable-xhdpi/pointer_spot_hover.png diff --git a/core/res/res/drawable-xhdpi/pointer_spot_touch.png b/core/res/res/drawable-xhdpi/pointer_spot_touch.png Binary files differindex 2e922db4a778..439405099ae4 100644 --- a/core/res/res/drawable-xhdpi/pointer_spot_touch.png +++ b/core/res/res/drawable-xhdpi/pointer_spot_touch.png diff --git a/core/res/res/drawable-xhdpi/pointer_text.png b/core/res/res/drawable-xhdpi/pointer_text.png Binary files differindex 0cebeae84cc9..2c0184ddab01 100644 --- a/core/res/res/drawable-xhdpi/pointer_text.png +++ b/core/res/res/drawable-xhdpi/pointer_text.png diff --git a/core/res/res/drawable-xhdpi/pointer_text_large.png b/core/res/res/drawable-xhdpi/pointer_text_large.png Binary files differindex d9ce209b6210..97db3ec28bca 100644 --- a/core/res/res/drawable-xhdpi/pointer_text_large.png +++ b/core/res/res/drawable-xhdpi/pointer_text_large.png 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 Binary files differindex 5454a8ba5ccb..a36deb3f4995 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 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 Binary files differindex fcfa405afc30..6870e23ae817 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 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 Binary files differindex a4268e4c3be4..c8d6d1f14a8a 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 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 Binary files differindex 39c5f1ae6cf7..5bfb7712f59d 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 diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png Binary files differindex 95ca954d5d71..720df913f9dc 100644 --- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png +++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png 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 Binary files differindex 191f1039f788..82b30d1fedc2 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 diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text.png b/core/res/res/drawable-xhdpi/pointer_vertical_text.png Binary files differindex a07d091395a2..4ec8eea041e2 100644 --- a/core/res/res/drawable-xhdpi/pointer_vertical_text.png +++ b/core/res/res/drawable-xhdpi/pointer_vertical_text.png diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png Binary files differindex d3f729a3d686..fab741220b83 100644 --- a/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png +++ b/core/res/res/drawable-xhdpi/pointer_vertical_text_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_0.png b/core/res/res/drawable-xhdpi/pointer_wait_0.png Binary files differindex 013aaf6b92dc..4c558b21ca1f 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_0.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_0.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_1.png b/core/res/res/drawable-xhdpi/pointer_wait_1.png Binary files differindex 7fb0300742d9..e86769dd3670 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_1.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_1.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_10.png b/core/res/res/drawable-xhdpi/pointer_wait_10.png Binary files differindex 90efa0a2c7c6..b0f9d87f4e17 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_10.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_10.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_11.png b/core/res/res/drawable-xhdpi/pointer_wait_11.png Binary files differindex 7c303a2fd1f7..4b853ab28a6b 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_11.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_11.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_12.png b/core/res/res/drawable-xhdpi/pointer_wait_12.png Binary files differindex 20a1fa88e05c..59bf8a88beea 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_12.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_12.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_13.png b/core/res/res/drawable-xhdpi/pointer_wait_13.png Binary files differindex 4585a3983673..5c618d7b0766 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_13.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_13.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_14.png b/core/res/res/drawable-xhdpi/pointer_wait_14.png Binary files differindex bdf0e5cd2826..c4fb4acd7cd7 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_14.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_14.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_15.png b/core/res/res/drawable-xhdpi/pointer_wait_15.png Binary files differindex e39c28cccd61..24bdc15ea7d2 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_15.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_15.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_16.png b/core/res/res/drawable-xhdpi/pointer_wait_16.png Binary files differindex 728814180fe9..a55f28084b4d 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_16.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_16.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_17.png b/core/res/res/drawable-xhdpi/pointer_wait_17.png Binary files differindex b35da18604bf..fc616455c21e 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_17.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_17.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_18.png b/core/res/res/drawable-xhdpi/pointer_wait_18.png Binary files differindex 0a61dd6ef078..6f72345cea09 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_18.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_18.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_19.png b/core/res/res/drawable-xhdpi/pointer_wait_19.png Binary files differindex 1920c774a987..05d2dbba6c36 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_19.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_19.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_2.png b/core/res/res/drawable-xhdpi/pointer_wait_2.png Binary files differindex c6b3861f9e60..5b018a36b1b3 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_2.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_2.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_20.png b/core/res/res/drawable-xhdpi/pointer_wait_20.png Binary files differindex b27358b2b921..af141d8d3cd8 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_20.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_20.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_21.png b/core/res/res/drawable-xhdpi/pointer_wait_21.png Binary files differindex c1f907691563..90cd89ca23b8 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_21.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_21.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_22.png b/core/res/res/drawable-xhdpi/pointer_wait_22.png Binary files differindex 6cc0c1346918..41165cb15f1e 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_22.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_22.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_23.png b/core/res/res/drawable-xhdpi/pointer_wait_23.png Binary files differindex 7df525cf4f2d..46305db3bf71 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_23.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_23.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_24.png b/core/res/res/drawable-xhdpi/pointer_wait_24.png Binary files differindex 008e273d21b9..53ecd9b4178a 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_24.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_24.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_25.png b/core/res/res/drawable-xhdpi/pointer_wait_25.png Binary files differindex 59f02d95014c..e41399799c27 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_25.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_25.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_26.png b/core/res/res/drawable-xhdpi/pointer_wait_26.png Binary files differindex 852b0bf7780f..081ef8402a74 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_26.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_26.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_27.png b/core/res/res/drawable-xhdpi/pointer_wait_27.png Binary files differindex aba6292c4293..19f29acf2c01 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_27.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_27.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_28.png b/core/res/res/drawable-xhdpi/pointer_wait_28.png Binary files differindex e59af4322771..9f9f9068699b 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_28.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_28.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_29.png b/core/res/res/drawable-xhdpi/pointer_wait_29.png Binary files differindex a195c29eae1d..07ff05e265d9 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_29.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_29.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_3.png b/core/res/res/drawable-xhdpi/pointer_wait_3.png Binary files differindex fb40e6f3084c..e77332e263f9 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_3.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_3.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_30.png b/core/res/res/drawable-xhdpi/pointer_wait_30.png Binary files differindex 0d19d280118f..7d3141681094 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_30.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_30.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_31.png b/core/res/res/drawable-xhdpi/pointer_wait_31.png Binary files differindex 49066b6c6827..651a33d7ade6 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_31.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_31.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_32.png b/core/res/res/drawable-xhdpi/pointer_wait_32.png Binary files differindex 43f75431c8e4..b09b96199ef7 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_32.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_32.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_33.png b/core/res/res/drawable-xhdpi/pointer_wait_33.png Binary files differindex 3664ea80b2dd..5b260c4aefd2 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_33.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_33.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_34.png b/core/res/res/drawable-xhdpi/pointer_wait_34.png Binary files differindex e4c3919ed6dc..fdcfc64bd30f 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_34.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_34.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_35.png b/core/res/res/drawable-xhdpi/pointer_wait_35.png Binary files differindex e58777adfeb8..e3da4b815274 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_35.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_35.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_36.png b/core/res/res/drawable-xhdpi/pointer_wait_36.png Binary files differnew file mode 100644 index 000000000000..1a1d7b782f15 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_36.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_37.png b/core/res/res/drawable-xhdpi/pointer_wait_37.png Binary files differnew file mode 100644 index 000000000000..c53c43c45786 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_37.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_38.png b/core/res/res/drawable-xhdpi/pointer_wait_38.png Binary files differnew file mode 100644 index 000000000000..16cf47f028bb --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_38.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_39.png b/core/res/res/drawable-xhdpi/pointer_wait_39.png Binary files differnew file mode 100644 index 000000000000..2adb86175188 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_39.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_4.png b/core/res/res/drawable-xhdpi/pointer_wait_4.png Binary files differindex 552b7354525a..18bd82699ae7 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_4.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_4.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_40.png b/core/res/res/drawable-xhdpi/pointer_wait_40.png Binary files differnew file mode 100644 index 000000000000..da7861f77810 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_40.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_41.png b/core/res/res/drawable-xhdpi/pointer_wait_41.png Binary files differnew file mode 100644 index 000000000000..d0ba24265359 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_41.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_42.png b/core/res/res/drawable-xhdpi/pointer_wait_42.png Binary files differnew file mode 100644 index 000000000000..a90a13803dbd --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_42.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_43.png b/core/res/res/drawable-xhdpi/pointer_wait_43.png Binary files differnew file mode 100644 index 000000000000..4256b255ed0c --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_43.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_44.png b/core/res/res/drawable-xhdpi/pointer_wait_44.png Binary files differnew file mode 100644 index 000000000000..9e5fe21fc7ac --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_44.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_45.png b/core/res/res/drawable-xhdpi/pointer_wait_45.png Binary files differnew file mode 100644 index 000000000000..19dd43f2dfc2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_45.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_46.png b/core/res/res/drawable-xhdpi/pointer_wait_46.png Binary files differnew file mode 100644 index 000000000000..c91001fe8711 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_46.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_47.png b/core/res/res/drawable-xhdpi/pointer_wait_47.png Binary files differnew file mode 100644 index 000000000000..d481e422e951 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_47.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_48.png b/core/res/res/drawable-xhdpi/pointer_wait_48.png Binary files differnew file mode 100644 index 000000000000..299585b3c9a5 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_48.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_49.png b/core/res/res/drawable-xhdpi/pointer_wait_49.png Binary files differnew file mode 100644 index 000000000000..cd032133dfc9 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_49.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_5.png b/core/res/res/drawable-xhdpi/pointer_wait_5.png Binary files differindex cd2bfa153a13..3abd5dbc89df 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_5.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_5.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_50.png b/core/res/res/drawable-xhdpi/pointer_wait_50.png Binary files differnew file mode 100644 index 000000000000..72122a0dfb0e --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_50.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_51.png b/core/res/res/drawable-xhdpi/pointer_wait_51.png Binary files differnew file mode 100644 index 000000000000..94398fd3112f --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_51.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_52.png b/core/res/res/drawable-xhdpi/pointer_wait_52.png Binary files differnew file mode 100644 index 000000000000..a4561bd2029d --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_52.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_53.png b/core/res/res/drawable-xhdpi/pointer_wait_53.png Binary files differnew file mode 100644 index 000000000000..a8f417adff94 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_53.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_54.png b/core/res/res/drawable-xhdpi/pointer_wait_54.png Binary files differnew file mode 100644 index 000000000000..cedab098c29b --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_54.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_55.png b/core/res/res/drawable-xhdpi/pointer_wait_55.png Binary files differnew file mode 100644 index 000000000000..76e1bb62f070 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_55.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_56.png b/core/res/res/drawable-xhdpi/pointer_wait_56.png Binary files differnew file mode 100644 index 000000000000..c4f1d1296a74 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_56.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_57.png b/core/res/res/drawable-xhdpi/pointer_wait_57.png Binary files differnew file mode 100644 index 000000000000..46ab8e232667 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_57.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_58.png b/core/res/res/drawable-xhdpi/pointer_wait_58.png Binary files differnew file mode 100644 index 000000000000..9d369572325b --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_58.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_59.png b/core/res/res/drawable-xhdpi/pointer_wait_59.png Binary files differnew file mode 100644 index 000000000000..c0f342b18ee4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_59.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_6.png b/core/res/res/drawable-xhdpi/pointer_wait_6.png Binary files differindex f7c71b935496..ad5ecb40501f 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_6.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_6.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_60.png b/core/res/res/drawable-xhdpi/pointer_wait_60.png Binary files differnew file mode 100644 index 000000000000..202c5121d8f5 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_60.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_61.png b/core/res/res/drawable-xhdpi/pointer_wait_61.png Binary files differnew file mode 100644 index 000000000000..c48c3f2d9a06 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_61.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_62.png b/core/res/res/drawable-xhdpi/pointer_wait_62.png Binary files differnew file mode 100644 index 000000000000..80dee225b700 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_62.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_63.png b/core/res/res/drawable-xhdpi/pointer_wait_63.png Binary files differnew file mode 100644 index 000000000000..d5b0ee594e60 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_63.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_64.png b/core/res/res/drawable-xhdpi/pointer_wait_64.png Binary files differnew file mode 100644 index 000000000000..b327a5739dc9 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_64.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_65.png b/core/res/res/drawable-xhdpi/pointer_wait_65.png Binary files differnew file mode 100644 index 000000000000..92d832ba7888 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_65.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_66.png b/core/res/res/drawable-xhdpi/pointer_wait_66.png Binary files differnew file mode 100644 index 000000000000..253b8a0e1be4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_66.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_67.png b/core/res/res/drawable-xhdpi/pointer_wait_67.png Binary files differnew file mode 100644 index 000000000000..a8b3e4ec3c1f --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_67.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_68.png b/core/res/res/drawable-xhdpi/pointer_wait_68.png Binary files differnew file mode 100644 index 000000000000..cfd03439fe58 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_68.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_69.png b/core/res/res/drawable-xhdpi/pointer_wait_69.png Binary files differnew file mode 100644 index 000000000000..cb01ee5694f2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_69.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_7.png b/core/res/res/drawable-xhdpi/pointer_wait_7.png Binary files differindex 3fa202e8e006..bca504ec53a9 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_7.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_7.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_70.png b/core/res/res/drawable-xhdpi/pointer_wait_70.png Binary files differnew file mode 100644 index 000000000000..f38082f0566c --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_70.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_71.png b/core/res/res/drawable-xhdpi/pointer_wait_71.png Binary files differnew file mode 100644 index 000000000000..e8b56dbe0f9e --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_71.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_72.png b/core/res/res/drawable-xhdpi/pointer_wait_72.png Binary files differnew file mode 100644 index 000000000000..9dff9e069adf --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_72.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_73.png b/core/res/res/drawable-xhdpi/pointer_wait_73.png Binary files differnew file mode 100644 index 000000000000..e989fcc7f485 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_73.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_74.png b/core/res/res/drawable-xhdpi/pointer_wait_74.png Binary files differnew file mode 100644 index 000000000000..953bbd30af10 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_74.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_75.png b/core/res/res/drawable-xhdpi/pointer_wait_75.png Binary files differnew file mode 100644 index 000000000000..7df2d9e523f0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_75.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_76.png b/core/res/res/drawable-xhdpi/pointer_wait_76.png Binary files differnew file mode 100644 index 000000000000..b241ef6d9b87 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_76.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_77.png b/core/res/res/drawable-xhdpi/pointer_wait_77.png Binary files differnew file mode 100644 index 000000000000..f5018b8d9085 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_77.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_78.png b/core/res/res/drawable-xhdpi/pointer_wait_78.png Binary files differnew file mode 100644 index 000000000000..06209a01f726 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_78.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_79.png b/core/res/res/drawable-xhdpi/pointer_wait_79.png Binary files differnew file mode 100644 index 000000000000..37887b8735a8 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_79.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_8.png b/core/res/res/drawable-xhdpi/pointer_wait_8.png Binary files differindex e0e50ae28b3f..08449440595d 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_8.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_8.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_80.png b/core/res/res/drawable-xhdpi/pointer_wait_80.png Binary files differnew file mode 100644 index 000000000000..115ef9dfbc1b --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_wait_80.png diff --git a/core/res/res/drawable-xhdpi/pointer_wait_9.png b/core/res/res/drawable-xhdpi/pointer_wait_9.png Binary files differindex e3de26f7cb47..bf6d02247e85 100644 --- a/core/res/res/drawable-xhdpi/pointer_wait_9.png +++ b/core/res/res/drawable-xhdpi/pointer_wait_9.png diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in.png b/core/res/res/drawable-xhdpi/pointer_zoom_in.png Binary files differindex 9c29fcb528c4..d436387649d9 100644 --- a/core/res/res/drawable-xhdpi/pointer_zoom_in.png +++ b/core/res/res/drawable-xhdpi/pointer_zoom_in.png diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png b/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png Binary files differindex beabbd23ae44..e451a33210e7 100644 --- a/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png +++ b/core/res/res/drawable-xhdpi/pointer_zoom_in_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out.png b/core/res/res/drawable-xhdpi/pointer_zoom_out.png Binary files differindex 710552b66fbc..afaaedc80b18 100644 --- a/core/res/res/drawable-xhdpi/pointer_zoom_out.png +++ b/core/res/res/drawable-xhdpi/pointer_zoom_out.png diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png b/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png Binary files differindex b748f07efa32..0ed8327febb4 100644 --- a/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png +++ b/core/res/res/drawable-xhdpi/pointer_zoom_out_large.png diff --git a/core/res/res/drawable-xxhdpi/pointer_alias.png b/core/res/res/drawable-xxhdpi/pointer_alias.png Binary files differnew file mode 100644 index 000000000000..c329002ee075 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_alias.png diff --git a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png Binary files differnew file mode 100644 index 000000000000..808143aeff08 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png diff --git a/core/res/res/drawable-xxhdpi/pointer_arrow.png b/core/res/res/drawable-xxhdpi/pointer_arrow.png Binary files differindex 4031f165d114..56dfedc66b5c 100644 --- a/core/res/res/drawable-xxhdpi/pointer_arrow.png +++ b/core/res/res/drawable-xxhdpi/pointer_arrow.png diff --git a/core/res/res/drawable-xxhdpi/pointer_cell.png b/core/res/res/drawable-xxhdpi/pointer_cell.png Binary files differnew file mode 100644 index 000000000000..4180882b6581 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_cell.png diff --git a/core/res/res/drawable-xxhdpi/pointer_context_menu.png b/core/res/res/drawable-xxhdpi/pointer_context_menu.png Binary files differnew file mode 100644 index 000000000000..6ebfaab9abed --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_context_menu.png diff --git a/core/res/res/drawable-xxhdpi/pointer_copy.png b/core/res/res/drawable-xxhdpi/pointer_copy.png Binary files differnew file mode 100644 index 000000000000..bef4fb4e8c17 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_copy.png diff --git a/core/res/res/drawable-xxhdpi/pointer_crosshair.png b/core/res/res/drawable-xxhdpi/pointer_crosshair.png Binary files differnew file mode 100644 index 000000000000..6cb8b835506e --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_crosshair.png diff --git a/core/res/res/drawable-xxhdpi/pointer_grab.png b/core/res/res/drawable-xxhdpi/pointer_grab.png Binary files differnew file mode 100644 index 000000000000..6caa1ba77eb9 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_grab.png diff --git a/core/res/res/drawable-xxhdpi/pointer_grabbing.png b/core/res/res/drawable-xxhdpi/pointer_grabbing.png Binary files differnew file mode 100644 index 000000000000..b52f75174a16 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-xxhdpi/pointer_hand.png b/core/res/res/drawable-xxhdpi/pointer_hand.png Binary files differnew file mode 100644 index 000000000000..f3ee077b5cc9 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_hand.png diff --git a/core/res/res/drawable-xxhdpi/pointer_help.png b/core/res/res/drawable-xxhdpi/pointer_help.png Binary files differnew file mode 100644 index 000000000000..96b2a711d9e7 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_help.png diff --git a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..677ccadbb26a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png diff --git a/core/res/res/drawable-xxhdpi/pointer_nodrop.png b/core/res/res/drawable-xxhdpi/pointer_nodrop.png Binary files differnew file mode 100644 index 000000000000..ef54301e4eac --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png b/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png Binary files differnew file mode 100644 index 000000000000..5b46ce5bfb75 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_spot_anchor.png diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_hover.png b/core/res/res/drawable-xxhdpi/pointer_spot_hover.png Binary files differnew file mode 100644 index 000000000000..6c1ab15c0a93 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_spot_hover.png diff --git a/core/res/res/drawable-xxhdpi/pointer_spot_touch.png b/core/res/res/drawable-xxhdpi/pointer_spot_touch.png Binary files differnew file mode 100644 index 000000000000..ab69fd82b7b0 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_spot_touch.png diff --git a/core/res/res/drawable-xxhdpi/pointer_text.png b/core/res/res/drawable-xxhdpi/pointer_text.png Binary files differnew file mode 100644 index 000000000000..01458862d25a --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_text.png 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 Binary files differnew file mode 100644 index 000000000000..e01aa649780c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png 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 Binary files differnew file mode 100644 index 000000000000..e947e0ea6f14 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png Binary files differnew file mode 100644 index 000000000000..c867247c08f4 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_text.png b/core/res/res/drawable-xxhdpi/pointer_vertical_text.png Binary files differnew file mode 100644 index 000000000000..77ff389ca8cc --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_vertical_text.png diff --git a/core/res/res/drawable-xxhdpi/pointer_zoom_in.png b/core/res/res/drawable-xxhdpi/pointer_zoom_in.png Binary files differnew file mode 100644 index 000000000000..7852abe51bca --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_zoom_in.png diff --git a/core/res/res/drawable-xxhdpi/pointer_zoom_out.png b/core/res/res/drawable-xxhdpi/pointer_zoom_out.png Binary files differnew file mode 100644 index 000000000000..83c621ebdc41 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_zoom_out.png diff --git a/core/res/res/drawable/pointer_alias_icon.xml b/core/res/res/drawable/pointer_alias_icon.xml index 8ba930165b48..4c701c0481f7 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 149f58cf5d01..8dca8a5e54e2 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 e9469488be3a..a0cf3182e89d 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 c580f765cfef..1fe7dadb1702 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 72af0c11f50c..61eb1cd69d44 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 22c7bfebf951..0f2997bc236b 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 da1e320c1d71..35a61c2fce1b 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 330b627b5d02..badc73612c1f 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 c57e615feb94..e07e5b607d65 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 e299db5b23b5..da32939b5b0f 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 4ee3f18f0c4b..55d47b4f4d63 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 6a71b7b638f3..1073fff1c243 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 d437b3a7bf8a..b3d4e78828cd 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 f2df1cb48381..343c7d27c749 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 38f4c3a064fa..93818d75925c 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 e4042bf66aa4..ac1626530778 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 3d90b88d5dca..3f9d1a639ddb 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 e34422af6c13..cd49762bd23f 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 49ae554249f8..0a4bdb03b524 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 4c60a55a6458..43a12616a540 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 93b3fe500d9c..6ddcc42a390f 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 a2039e602573..854be260f43f 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 955b40f2d567..4dffd23638da 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 cde2e4131926..602c744401a9 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 73c0c11d99fa..b7d000f4511f 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 1d7440b3f6d9..0d641b2cbe6c 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 f4f0639d49d8..b1c8c0bd409f 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 d948c8916963..1daba6559fb5 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 24d35b0ba2b4..5b32ea34843b 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 de5efe25dae2..1598766eab7f 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 270ccc9ce721..5e0d37bb51ce 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 e87b52686426..83324ef9e8de 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 e350a4385d48..6ed5a0ba2829 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 5759079a47fa..c746fd8a4855 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 65728ad3374d..190d181cb14d 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 3b48dc89fb56..275b979751eb 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 48211cb7a17b..a1b5dc213b03 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 8955ce8dd418..c769be56ca62 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 d9b03b096093..e52c4e06f787 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 e2dcb49c1600..322fab83e820 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 3eb89f5680c3..4505892039aa 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 b805df3d18c1..bfb5aa647377 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 df6412e049b2..90057e6e38cd 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 000000000000..f9605af10985 --- /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 000000000000..8857b5f98087 --- /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 000000000000..0f3fb420a858 --- /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 000000000000..aa14c0866310 --- /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 000000000000..58876612a5ea --- /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 000000000000..5aed20e042f7 --- /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 000000000000..27fff7595f07 --- /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 5472316487fb..c7caa395f322 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 3172f735f3fc..6e84f390400c 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 e2261af28e30..8a2ce5da5985 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 1db006f970d7..c4c1ed9b9998 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 @@ a similar way. <!-- 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 180a312a3f2b..bf8c7c63590f 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 @@ public class BugreportManagerTest { 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 bf8ca8bc3764..4cccf8e49890 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 3b8b8c797241..4a12bb374cb2 100644 --- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java +++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java @@ -142,6 +142,31 @@ public class DisplayTest { } @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 361dc594f2c6..e04f13c9b922 100644 --- a/graphics/java/android/graphics/HardwareBufferRenderer.java +++ b/graphics/java/android/graphics/HardwareBufferRenderer.java @@ -275,11 +275,22 @@ public class HardwareBufferRenderer implements AutoCloseable { 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 563fb4d88941..87a7c3edf826 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 3d1ed87f1305..14daae03e6b5 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 @@ public abstract class TvPipModule { @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 e2cd7a0d1d77..d8e2f5c4a817 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.DesktopModeStatus; 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 @@ public abstract class WMShellModule { 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 @@ public abstract class WMShellModule { @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 015d5c1705e7..fb0a91f17802 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.View; 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 @@ public class DesktopModeVisualIndicator { 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 @@ public class DesktopModeVisualIndicator { 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 @@ public class DesktopModeVisualIndicator { 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 @@ public class DesktopModeVisualIndicator { 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 @@ public class DesktopModeVisualIndicator { }); } } + + /** + * 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 @@ public class DesktopModeVisualIndicator { /** * 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 toFullscreenAnimator(@NonNull View view, + @NonNull DisplayLayout displayLayout) { + final Rect bounds = getMaxBounds(displayLayout); + final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( + view, bounds, bounds); + animator.setInterpolator(new DecelerateInterpolator()); + 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 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 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; + } + + /** + * 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 + */ + 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()); - setupFullscreenIndicatorAnimation(animator); + setupIndicatorAnimation(animator); return animator; } /** - * Add necessary listener for animation of fullscreen indicator + * Add necessary listener for animation of indicator */ - private static void setupFullscreenIndicatorAnimation( - VisualIndicatorAnimator animator) { + private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) { animator.addUpdateListener(a -> { if (animator.mView != null) { animator.updateBounds(a.getAnimatedFraction(), animator.mView); @@ -196,7 +283,7 @@ public class DesktopModeVisualIndicator { 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 @@ public class DesktopModeVisualIndicator { * @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 @@ public class DesktopModeVisualIndicator { 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 cb04a43e7205..670b24c176b5 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } /** 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( } /** 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 @@ class DesktopTasksController( } } + 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 @@ class DesktopTasksController( 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 000000000000..d18e98af0988 --- /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 4c53f607a5f8..f70df833cf4f 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 @@ public class PipAnimationController { 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 f2f30ea7a286..a9392120bcd9 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.TaskOrganizer; 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** + * 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** * 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSplitScreenOptional.ifPresent(splitScreenController -> splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct)); } else { - mTaskOrganizer.applyTransaction(wct); + mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback); } } @@ -1790,6 +1854,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 49a27c57dc73..4a76a502462c 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 class PipTransition extends PipTransitionController { 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 @@ public class PipTransition extends PipTransitionController { 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 @@ public class PipTransition extends PipTransitionController { 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 @@ public class PipTransition extends PipTransitionController { 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 1d12824e7dcf..68952c0df52f 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 @@ public class PipResizeGestureHandler { 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 d73723cc02ff..2f74fb636bc9 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 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: PiP has already been closed by custom close action", TAG); return; } - removeTask(mPinnedTaskId); + mPipTaskOrganizer.removePip(); onPipDisappeared(); } @@ -673,17 +673,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } } - 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 f6856f15f16f..0940490e9944 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 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { 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 8ebcf63f36e9..d3253a5e4d94 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); - } - - @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; + PipAnimationController pipAnimationController, + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + Optional<SplitScreenController> splitScreenOptional) { + super(context, shellInit, shellTaskOrganizer, transitions, tvPipBoundsState, + pipDisplayLayoutState, pipTransitionState, tvPipMenuController, + tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, + splitScreenOptional); } - @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 5c64177ae835..c8d6a5e8e00b 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { 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 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } 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 d52abf795152..5a92f7830194 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 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, 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 63c7969291a0..3dd10a098310 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 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { 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 485b400f458d..4e3d220f1ea2 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 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { @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 class OneShotRemoteHandler implements Transitions.TransitionHandler { 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 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { + " 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 3c4e8898f215..5b7231c5a5fb 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 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } 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 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } } } - 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 class RemoteTransitionHandler implements Transitions.TransitionHandler { 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 0386ec38a3ff..1879bf721fed 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 @@ package com.android.wm.shell.transition; 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 @@ class SleepHandler implements Transitions.TransitionHandler { @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 4284993a5448..681fa5177da2 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 @@ public class Transitions implements RemoteCallable<Transitions> { /** 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 @@ public class Transitions implements RemoteCallable<Transitions> { /** 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { && 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { } 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { /** 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { // 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 7595c9617709..ce102917352d 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.view.WindowManager.TRANSIT_TO_FRONT; 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 @@ public class TransitionUtil { && !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 c0dcd0b68c6f..f99821747fef 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 @@ import java.util.function.Supplier; 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @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 8cb575cc96e3..2cd1e1d558ae 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 android.view.WindowManagerGlobal; 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 @@ class DragResizeInputListener implements AutoCloseable { 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 91846fafd1db..e986ee127708 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 @@ fun FlickerTest.splitAppLayerBoundsChanges( 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 d0bca1332553..2474ecf74cf9 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.filters.RequiresDevice 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 @@ import org.junit.runners.Parameterized @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 5e85eb87e0e9..000000000000 --- 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 1045a5ac2ce8..93ee6992a98f 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 @@ import org.junit.runners.Parameterized * 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 2d2588ef4348..59918fb7b6a9 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 @@ import org.junit.runners.Parameterized * 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 6c5a344c8f79..36c6f7c438c4 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 @@ package com.android.wm.shell.flicker.pip 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 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) @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 e540ad543228..d16583271e8c 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 @@ import org.junit.runners.Parameterized * 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 e079d5477e2f..db18edba9cc4 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 @@ import org.junit.runners.Parameterized * 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 e40e5eaad9e2..51f01364ec9c 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 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) @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 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) 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 1f060e931be2..f1925d8c9d85 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 @@ import org.junit.runners.Parameterized * 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 313631cbe8ee..3e0e37dfc997 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 @@ import org.junit.runners.Parameterized * 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 @@ open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppT 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 93ffdd8d5294..603f99541a12 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 @@ import org.junit.runners.Parameterized * 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 @@ open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransit 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 7d5f740838bd..6deba1b68f38 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 @@ import org.junit.runners.Parameterized * 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 9c007449fb8d..d8d57d219933 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 @@ import org.junit.runners.Parameterized * 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 c23838a987bf..a626713aaa11 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 @@ package com.android.wm.shell.flicker.pip 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 android.tools.device.helpers.WindowUtils 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 @@ import org.junit.runners.Parameterized 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 6f8111690f0f..000000000000 --- 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 c8d5624b1d77..ae3f87967658 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 @@ import org.junit.runners.Parameterized * 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 083cfd294f96..4e2a4e700698 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.runner.RunWith 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 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { pipApp.exit(wmHelper) tapl.setEnableRotation(false) } - transitions { - pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) - } + transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) } } @Postsubmit @@ -92,4 +88,4 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { 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 53ce3936fbe4..9fe9f52fd4af 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.runner.RunWith 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 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker){ // 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 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker){ ) } } -}
\ 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 2cf8f61f13fe..703784dd8c67 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 @@ import org.junit.runners.Parameterized * 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 17f174b2195f..5180791276a2 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 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) { } } - @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 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) { 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 5b06c9c3897d..69da1e29a19c 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 @@ class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase( @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 c8401831bdbc..1773846c18e9 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 @@ class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreen @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 @@ class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreen @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 5c9920970761..3bea66ef0a27 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 @@ class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase( @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 @@ class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase( @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 2855c71518eb..9f4cb8c381fc 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.FlakyTest 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.layerBecomesVisible 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 @@ class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) @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 c29a917c4e7c..a33d8cab9fbd 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.FlakyTest 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.layerBecomesVisible 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 @@ class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicke @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 4ccc4678ac5e..c9bd695ffb33 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 @@ class DesktopTasksControllerTest : ShellTestCase() { @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 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 000000000000..2c5a5cd72c53 --- /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 5b62a940c074..ada3455fae18 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 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { 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 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { 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 1b291468d6dc..a9f311f9e9eb 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 @@ public class SplitTransitionTests extends ShellTestCase { 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 @@ public class SplitTransitionTests extends ShellTestCase { 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 60d697823f64..5cd548bfe5ab 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 @@ public class ShellTransitionTests extends ShellTestCase { 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 @@ public class ShellTransitionTests extends ShellTestCase { 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 @@ public class ShellTransitionTests extends ShellTestCase { 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 3b129720c727..5d79104200d9 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -738,6 +738,9 @@ cc_test { "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 768dfcd52840..706f18c3be80 100644 --- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp @@ -85,28 +85,20 @@ static void HardwareBufferRenderer_destroy(jlong renderProxy) { } 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 @@ static int android_graphics_HardwareBufferRenderer_render(JNIEnv* env, jobject, 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 202a62cf320c..cc987bcd8f0e 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -69,15 +69,9 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { } 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 99298bc0fe9b..c8f2e69ae0a4 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -66,15 +66,8 @@ MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { } 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 dd781bb85470..6b2c99534a4c 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -528,6 +528,14 @@ void CanvasContext::notifyFramePending() { 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 @@ void CanvasContext::draw() { 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 b26c018e86fb..3f2533959c20 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -264,6 +264,8 @@ private: 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 91fe3f6cf273..8c942d0fa102 100644 --- a/libs/hwui/renderthread/HardwareBufferRenderParams.h +++ b/libs/hwui/renderthread/HardwareBufferRenderParams.h @@ -36,9 +36,12 @@ class RenderProxy; 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 @@ public: } } + 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 3813e97e04fc..3f3ad759ed07 100644 --- a/location/java/android/location/GnssMeasurementRequest.java +++ b/location/java/android/location/GnssMeasurementRequest.java @@ -135,8 +135,12 @@ public final class GnssMeasurementRequest implements Parcelable { 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 dcf39458b261..47d84264a9cd 100644 --- a/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/IInjectGlobalEvent.aidl @@ -25,14 +25,14 @@ import android.media.soundtrigger_middleware.IAcknowledgeEvent; 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 @@ oneway interface IInjectGlobalEvent { */ 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 37050df160e3..62c4a5167316 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -29,6 +29,7 @@ import android.media.AudioAttributes; 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 @@ public final class MediaController { * @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 528ac12c16b7..ebd753389843 100644 --- a/native/android/tests/activitymanager/nativeTests/Android.bp +++ b/native/android/tests/activitymanager/nativeTests/Android.bp @@ -45,4 +45,7 @@ cc_test { required: [ "UidImportanceHelperApp", ], + data: [ + ":UidImportanceHelperApp", + ], } diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index c898fe5a9f2c..d87abb98ebde 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 ae0882342be4..4154029b6d41 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -551,7 +551,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements 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 28f9453a48a2..452455c9838c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -110,6 +110,11 @@ class CredentialManagerRepo( 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 @@ class CredentialManagerRepo( isPasskeyFirstUse )!!, getCredentialUiState = null, + cancelRequestState = cancelUiRequestState ) } RequestInfo.TYPE_GET -> { @@ -142,6 +148,7 @@ class CredentialManagerRepo( 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 @@ class CredentialManagerRepo( } } - /** 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 5d72424c8f8a..2efe1bee43cc 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.activity.viewModels 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 @@ class CredentialSelectorActivity : ComponentActivity() { 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 @@ class CredentialSelectorActivity : ComponentActivity() { 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 @@ class CredentialSelectorActivity : ComponentActivity() { } } + /** + * 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 @@ class CredentialSelectorActivity : ComponentActivity() { 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 @@ class CredentialSelectorActivity : ComponentActivity() { } 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 @@ class CredentialSelectorActivity : ComponentActivity() { ) 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 e49e3f165cfc..7eb3bf46b493 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -51,6 +51,11 @@ data class UiState( // 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 @@ class CredentialSelectorViewModel( 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 783cf3b47344..43da9807231b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -64,7 +64,7 @@ import androidx.credentials.provider.RemoteEntry 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 514ff90be8d7..dfff3d694877 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.Icon 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 @@ fun Snackbar( } } } + 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 a3d632cfb82a..e884cf834722 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } // 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> @Override public String toString() { - return "CachedBluetoothDevice (" + return "CachedBluetoothDevice{" + "anonymizedAddress=" + mDevice.getAnonymizedAddress() + ", name=" + getName() + ", groupId=" + mGroupId - + ")"; + + ", member=" + mMemberDevices + + "}"; } @Override @@ -1482,6 +1484,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> * 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 20a6cd8e09ce..356bb82a92e0 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 @@ public class CsipDeviceManager { 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 @@ public class CsipDeviceManager { 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(); - } - - 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; - } + getPreferredMainDeviceWithoutConectionState(groupId, memberDevicesList); - 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 @@ public class CsipDeviceManager { 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 @@ public class CsipDeviceManager { continue; } - for (CachedBluetoothDevice memberDevice: memberSet) { + for (CachedBluetoothDevice memberDevice : memberSet) { if (memberDevice != null && memberDevice.equals(device)) { return cachedDevice; } @@ -310,7 +251,6 @@ public class CsipDeviceManager { * 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 @@ public class CsipDeviceManager { 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 1c179f838586..6444f3bd4341 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 @@ public class CachedBluetoothDeviceTest { 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 80030f7a7a47..02ec486a0205 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.annotation.NonNull; 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 @@ final class GenerationRegistry { 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 000000000000..0b7181606247 --- /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 d49627e5334a..d3a9e91c3da8 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 @@ class SettingsProtoDumpUtil { 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 5a8c59489ec8..7a97b78a4517 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 @@ public class SettingsProvider extends ContentProvider { } finally { Binder.restoreCallingIdentity(identity); } + mSettingsRegistry.mGenerationRegistry.dump(pw); } } @@ -2323,7 +2324,15 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 000000000000..28f25e0cc407 --- /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 ac75cc88fdf5..3007d4a79d13 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -182,7 +182,6 @@ android_library { "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 @@ android_library { "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 91cb4ba21720..462c90bcabf3 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 4b6f9a430390..02d279fa4962 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.net.Uri; 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 @@ public class A11yMenuSettingsActivity extends FragmentActivity { 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 a25790ad7aa0..5b7bbe80f9ad 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 @@ public class A11yMenuOverlayLayout { /** 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 @@ public class A11yMenuOverlayLayout { 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 03e1e66a3cac..197b217f96eb 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 @@ class RemoteTransitionAdapter { } @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 e73afe74c03d..a7e95b58a6e4 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.PluginLifecycleManager 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 @@ import kotlinx.coroutines.launch 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 @@ open class ClockRegistry( 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 @@ open class ClockRegistry( 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 @@ open class ClockRegistry( 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 @@ open class ClockRegistry( 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 @@ open class ClockRegistry( } 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 b49afeef09f3..4b9470728dc6 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 1f44f0532ead..cad2c162a589 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 b9e711e54b3b..d8967d4706ce 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 79948da978d7..4f6e88c2266f 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 4d6c2022c3b8..852db1b8fb91 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -108,11 +108,11 @@ asked for it --> 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 9ed3f92a7d74..4850b35833e5 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 324ba02a7a46..1dd12eef4639 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 08904658a27d..fac2f910a789 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 @@ public class Task { return this.baseIntent.getPackage(); } + public int getId() { + return id; + } + @Override public boolean equals(Object o) { if (!(o instanceof TaskKey)) { @@ -307,6 +311,10 @@ public class Task { 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 53fab69bd3b8..cab54d08b3ec 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 com.android.systemui.shared.system.TaskStackChangeListeners; import java.io.PrintWriter; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -244,7 +243,12 @@ public class RotationButtonController { 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 e08a604338d2..4269530f0151 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 @@ public final class InteractionJankMonitorWrapper { 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 @@ public final class InteractionJankMonitorWrapper { 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 6c59a941d4ce..4d7d0ea264a4 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 class QuickStepContract { 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 @@ public class QuickStepContract { 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 @@ public class QuickStepContract { 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 @@ public class QuickStepContract { 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_AWAKE) != 0) { + str.add("awake"); } - if ((flags & SYSUI_STATE_SCREEN_ON) != 0) { - str.add("screen_on"); + 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 44f9d43f5470..f094102ad88f 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 @@ public class RemoteAnimationTargetCompat { 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 58e7747a7a9f..1fbf743836a1 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 @@ public class RemoteTransitionCompat { } } }; - 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 31234cf2ab53..c22d689338a8 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 @@ object FlagsFactory { 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 @@ object FlagsFactory { @BoolRes resourceId: Int, name: String, namespace: String = "systemui", - teamfood: Boolean = false ): ResourceBooleanFlag { val flag = ResourceBooleanFlag( @@ -63,7 +61,7 @@ object FlagsFactory { 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 27c5699df70f..5502da146bba 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 @@ object FlagsFactory { 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 @@ object FlagsFactory { @BoolRes resourceId: Int, name: String, namespace: String = "systemui", - teamfood: Boolean = false ): ResourceBooleanFlag { val flag = ResourceBooleanFlag( @@ -63,7 +61,7 @@ object FlagsFactory { 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 9f2333d8f435..1980f70d63a4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -107,14 +107,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey // 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 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey @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 cdaed878cc3c..07333f79cc94 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -390,6 +390,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS 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 68b40ab233f6..5c56aab9a611 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -260,23 +260,21 @@ public class KeyguardPatternViewController mLockPatternView.setEnabled(true); mLockPatternView.clearPattern(); + displayDefaultSecurityMessage(); + } + + @Override + public void onResume(int reason) { + super.onResume(reason); // if the user is currently locked out, enforce it. long deadline = mLockPatternUtils.getLockoutAttemptDeadline( KeyguardUpdateMonitor.getCurrentUser()); if (deadline != 0) { handleAttemptLockout(deadline); - } else { - displayDefaultSecurityMessage(); } } @Override - public void onResume(int reason) { - super.onResume(reason); - reset(); - } - - @Override public void onPause() { super.onPause(); @@ -300,34 +298,38 @@ public class KeyguardPatternViewController @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 559db76748ed..ded1238742fb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -127,6 +127,11 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB @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 67874e13298c..87a775866faf 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.metrics.LogMaker; 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.BouncerUiEvent; 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 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard 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 f4c581552bc4..fd55d69badd1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -172,11 +172,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * 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 a678edc0eb06..ac0a3fd8dbc4 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.StatusBarState; 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 @@ public class KeyguardVisibilityHelper { 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 @@ public class KeyguardVisibilityHelper { 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 @@ public class KeyguardVisibilityHelper { .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 @@ public class KeyguardVisibilityHelper { // 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 1ae380e53c52..235a8bca6d1e 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 android.hardware.biometrics.BiometricSourceType.FINGERPRINT; 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme updateIsUdfpsEnrolled(); updateConfiguration(); updateKeyguardShowing(); - mUserUnlockedWithBiometric = false; mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); mIsDozing = mStatusBarStateController.isDozing(); @@ -270,11 +266,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mStatusBarStateController.removeCallback(mStatusBarStateListener); mKeyguardStateController.removeCallback(mKeyguardStateCallback); - if (mCancelDelayedUpdateVisibilityRunnable != null) { - mCancelDelayedUpdateVisibilityRunnable.run(); - mCancelDelayedUpdateVisibilityRunnable = null; - } - mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityStateChangeListener); } @@ -288,11 +279,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private void updateVisibility() { - if (mCancelDelayedUpdateVisibilityRunnable != null) { - mCancelDelayedUpdateVisibilityRunnable.run(); - mCancelDelayedUpdateVisibilityRunnable = null; - } - if (!mIsKeyguardShowing && !mIsDozing) { mView.setVisibility(View.INVISIBLE); return; @@ -300,9 +286,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } } - /** - * @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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } @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 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void onUnlockedChanged() { mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - updateUserUnlockedWithBiometric(); updateKeyguardShowing(); updateVisibility(); } @@ -573,9 +525,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); updateKeyguardShowing(); - if (mIsKeyguardShowing) { - updateUserUnlockedWithBiometric(); - } updateVisibility(); } @@ -694,7 +643,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme 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 cbc0a1ba2b39..ac30311e1f96 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_ENROL 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 @@ public class UdfpsController implements DozeReceiver, Dumpable { } 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 @@ public class UdfpsController implements DozeReceiver, Dumpable { } 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 414c2ecb70bc..f876affb2a9c 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 @@ class UdfpsControllerOverlay @JvmOverloads constructor( 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 063b41e8db0f..5101ad4c94bc 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 @@ constructor( private val keyguardStateControllerCallback: KeyguardStateController.Callback = object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + updatePauseAuth() + } override fun onLaunchTransitionFadingAwayChanged() { launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway updatePauseAuth() @@ -403,6 +406,15 @@ constructor( 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 701df8981ca2..334cf9318322 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 abstract class Classifier { 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 @@ public abstract class Classifier { 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 d17eadd163fc..8ec48b9e6829 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 @@ package com.android.systemui.classifier; 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 @@ class DiagonalClassifier extends FalsingClassifier { } 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 f8ee49add04b..15e2e9a916b9 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 @@ class DistanceClassifier extends FalsingClassifier { || 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 d8d2c98d7641..2fb6aaf2ec65 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 @@ public class TypeClassifier extends FalsingClassifier { 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 840982cbcc64..4a3710b1e108 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.BRIGHT 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 @@ class ZigZagClassifier extends FalsingClassifier { 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 e049ae09b1de..c312f6969b07 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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv 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 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } 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 ac1150e0fcb1..e8c97bf77271 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.service.controls.actions.ControlAction 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.controls.ui.SelectedItem 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 00a406e4dbc0..be428a84da2f 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 @@ class AllModel( } 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 7df08651d5ab..d629e3ea365e 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.ViewGroup 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.CustomIconCache 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 @@ import javax.inject.Inject * Activity for rearranging and removing controls for a given structure */ open class ControlsEditingActivity @Inject constructor( + featureFlags: FeatureFlags, @Main private val mainExecutor: Executor, private val controller: ControlsControllerImpl, private val userTracker: UserTracker, 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 @@ open class ControlsEditingActivity @Inject constructor( 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 @@ open class ControlsEditingActivity @Inject constructor( 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 @@ open class ControlsEditingActivity @Inject constructor( } private fun bindButtons() { + addControls = requireViewById<Button>(R.id.addControls).apply { + isEnabled = true + visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE + 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 @@ open class ControlsEditingActivity @Inject constructor( } } + 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 3e97d3132bc7..d3ffc9585335 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.widget.Toast 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.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.controls.ui.ControlsActivity -import com.android.systemui.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 @@ open class ControlsFavoritingActivity @Inject constructor( // 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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( ControlsAnimations.enterAnimation(structurePager).start() } } - }, Consumer { runnable -> cancelLoadRunnable = runnable }) + }, { runnable -> cancelLoadRunnable = runnable }) } } @@ -299,7 +321,8 @@ open class ControlsFavoritingActivity @Inject constructor( 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 @@ open class ControlsFavoritingActivity @Inject constructor( } 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 @@ open class ControlsFavoritingActivity @Inject constructor( 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 d65481a8a830..3455e6dbc656 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 @@ interface ControlsModel { * 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 @@ data class ControlInfoWrapper( 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 3808e73ca085..92aff0624bdc 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 @@ open class ControlsProviderSelectorActivity @Inject constructor( 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 0aeb12875ea1..cf0dcad5bf0d 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 @@ public class DozeBrightnessHostForwarder extends DozeMachine.Service.Delegate { 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 f0aefb5bc0df..7f0b16bca8b4 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 com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -150,7 +151,6 @@ public class DozeMachine { 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 @@ public class DozeMachine { 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 @@ public class DozeMachine { @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 25c2c39f3e25..8d4447285af2 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 androidx.annotation.VisibleForTesting; 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 @@ public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delega * 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 a0c490951199..f7773f1888b3 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 androidx.annotation.VisibleForTesting; 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 @@ public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service * 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 069344ff0e2d..d408472efca5 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 android.os.Handler; 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.sensors.AsyncSensorManager; 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 @@ public abstract class DozeModule { @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 74a49a8f3c88..c954f98ad36e 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 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve 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 43e4c62b60d6..7f44463f1191 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 @@ public class DreamOverlayTouchMonitor { completer.set(predecessor); } + + if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) { + stopMonitoring(false); + } }); return "DreamOverlayTouchMonitor::pop"; @@ -214,7 +218,12 @@ public class DreamOverlayTouchMonitor { @Override public void onPause(@NonNull LifecycleOwner owner) { - stopMonitoring(); + stopMonitoring(false); + } + + @Override + public void onDestroy(LifecycleOwner owner) { + stopMonitoring(true); } }; @@ -222,7 +231,7 @@ public class DreamOverlayTouchMonitor { * 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 @@ public class DreamOverlayTouchMonitor { /** * 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 @@ public class DreamOverlayTouchMonitor { mCurrentInputSession.dispose(); mCurrentInputSession = null; + mStopMonitoringPending = false; } @@ -257,6 +272,8 @@ public class DreamOverlayTouchMonitor { 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 000000000000..58b70b02e84f --- /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 dad0004613f6..b719126adcf8 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 @@ import dagger.Module; */ @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 000000000000..9e0ae4119f24 --- /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 095ae380d83a..0cd2791f3027 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 @@ object Flags { 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 @@ object Flags { // 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 @@ object Flags { @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 000000000000..801b1652e487 --- /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 eef7ccc65d95..107e685c4482 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 class KeyguardService extends Service { ); } - 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 @@ public class KeyguardService extends Service { 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 @@ public class KeyguardService extends Service { // 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 @@ public class KeyguardService extends Service { 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 364e79c3967c..ea5fc1a341bd 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @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 4cdcafd66424..8764f12ff9f5 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.DismissCallbackRegistry; 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 java.util.concurrent.Executor; 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 @@ import kotlinx.coroutines.CoroutineScope; 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 d5129a612b04..09002fded4b8 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 @@ interface BiometricSettingsRepository { */ 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 @@ constructor( ) ) + 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 @@ constructor( 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/KeyguardFaceAuthManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index a3268405a830..56e73980079d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -26,10 +26,14 @@ 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 @@ -37,6 +41,7 @@ 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 @@ -48,10 +53,19 @@ 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 @@ -59,25 +73,12 @@ 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) +interface DeviceEntryFaceAuthRepository { + /** Provide the current face authentication state for device entry. */ + val isAuthenticated: Flow<Boolean> - /** - * 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() + /** Whether face auth can run at this point. */ + val canRunFaceAuth: Flow<Boolean> /** Provide the current status of face authentication. */ val authenticationStatus: Flow<AuthenticationStatus> @@ -91,12 +92,23 @@ interface KeyguardFaceAuthManager { /** Current state of whether face authentication is running. */ val isAuthRunning: Flow<Boolean> - /** Is face detection supported. */ - val isDetectionSupported: 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 KeyguardFaceAuthManagerImpl +class DeviceEntryFaceAuthRepositoryImpl @Inject constructor( context: Context, @@ -108,13 +120,68 @@ constructor( 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, -) : KeyguardFaceAuthManager, Dumpable { - private var cancellationSignal: CancellationSignal? = null - private val lockscreenBypassEnabled: Boolean - get() = keyguardBypassController?.bypassEnabled ?: false +) : 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) { @@ -132,13 +199,138 @@ constructor( ) .boxed() .collect(Collectors.toSet()) - dumpManager.registerCriticalDumpable("KeyguardFaceAuthManagerImpl", this) + 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() } @@ -154,6 +346,7 @@ constructor( _isLockedOut.value = true } _authenticationStatus.value = errorStatus + _isAuthenticated.value = false if (errorStatus.isCancellationError()) { cancelNotReceivedHandlerJob?.cancel() applicationScope.launch { @@ -182,6 +375,7 @@ constructor( override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) { _authenticationStatus.value = SuccessAuthenticationStatus(result) + _isAuthenticated.value = true faceAuthLogger.faceAuthSuccess(result) onFaceAuthRequestCompleted() } @@ -190,7 +384,7 @@ constructor( private fun onFaceAuthRequestCompleted() { cancellationInProgress = false _isAuthRunning.value = false - cancellationSignal = null + authCancellationSignal = null } private val detectionCallback = @@ -202,7 +396,7 @@ constructor( private var cancellationInProgress = false private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null - override suspend fun authenticate(uiEvent: FaceAuthUiEvent) { + override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { if (_isAuthRunning.value) { faceAuthLogger.ignoredFaceAuthTrigger(uiEvent) return @@ -219,44 +413,48 @@ constructor( 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() - ) + 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() } } - override suspend fun detect() { + suspend fun detect() { if (!isDetectionSupported) { faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal) return } - if (_isAuthRunning.value) { - faceAuthLogger.skippingBecauseAlreadyRunning("detection") + if (_isAuthRunning.value || detectCancellationSignal != null) { + faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null) return } - cancellationSignal = CancellationSignal() + detectCancellationSignal = CancellationSignal() withContext(mainDispatcher) { // We always want to invoke face detect in the main thread. faceAuthLogger.faceDetectionStarted() faceManager?.detectFace( - cancellationSignal, + detectCancellationSignal, detectionCallback, FaceAuthenticateOptions.Builder().setUserId(currentUserId).build() ) @@ -266,10 +464,15 @@ constructor( private val currentUserId: Int get() = userRepository.getSelectedUserInfo().id + fun cancelDetection() { + detectCancellationSignal?.cancel() + detectCancellationSignal = null + } + override fun cancel() { - if (cancellationSignal == null) return + if (authCancellationSignal == null) return - cancellationSignal?.cancel() + authCancellationSignal?.cancel() cancelNotReceivedHandlerJob = applicationScope.launch { delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT) @@ -285,32 +488,14 @@ constructor( _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) + private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> { + return cond.distinctUntilChanged().onEach { + faceAuthLogger.observedConditionChanged(it, loggingContext) + } + } companion object { - const val TAG = "KeyguardFaceAuthManager" + const val TAG = "DeviceEntryFaceAuthRepository" /** * If no cancel signal has been received after this amount of time, assume that it is @@ -320,7 +505,7 @@ constructor( } override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println("KeyguardFaceAuthManagerImpl state:") + pw.println("DeviceEntryFaceAuthRepositoryImpl state:") pw.println(" cancellationInProgress: $cancellationInProgress") pw.println(" _isLockedOut.value: ${_isLockedOut.value}") pw.println(" _isAuthRunning.value: ${_isAuthRunning.value}") @@ -335,12 +520,21 @@ constructor( pw.println( " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}" ) - pw.println(" cancellationSignal: $cancellationSignal") + 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: $lockscreenBypassEnabled") + 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 4fa56ee8e4d2..52234b32b83a 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.channels.awaitClose 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 @@ interface DeviceEntryFingerprintAuthRepository { /** * 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 @@ constructor( 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 64e2a2cbd396..0b506cfa4716 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 @@ interface KeyguardBouncerRepository { 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 @@ constructor( 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/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt new file mode 100644 index 000000000000..3c66f2424c7b --- /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 33f4e2e24322..9212aa1c8ef5 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.KeyguardConstants 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.plugins.ActivityStarter 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 @@ constructor( /** 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 b1c5f8fa270b..eded9c1454f2 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 @@ package com.android.systemui.keyguard.shared.model 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 468a6b52c5e5..172f9226f252 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 @@ object KeyguardBouncerViewBinder { 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 647e3a15ba2f..f7355d5c11e2 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.dagger.SysUISingleton 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 @@ constructor( ) } - 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 @@ constructor( { "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 b98a92ff8600..d848b431bcc9 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 androidx.annotation.NonNull; 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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { } 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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { @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 @@ public class SessionTracker implements CoreStartable { 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 @@ public class SessionTracker implements CoreStartable { 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 e204defb82b6..3775e2c6bbee 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 @@ public class LogModule { /** * 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 f92a5abdbf23..731bb2f4db7c 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 @@ public abstract class MediaOutputBaseAdapter extends protected final MediaOutputController mController; + private static final int UNMUTE_DEFAULT_VOLUME = 2; + Context mContext; View mHolderView; boolean mIsDragging; @@ -193,10 +195,6 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends 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 e352c613ad86..1894bc4cfeab 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.content.Context 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.SharingStarted 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 @@ import kotlinx.coroutines.flow.stateIn * 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 @@ constructor( started = SharingStarted.Eagerly, initialValue = false, ) + private val isBouncerShowing: StateFlow<Boolean> = keyguardTransitionInteractor .transitionValue(state = KeyguardState.PRIMARY_BOUNCER) @@ -102,23 +124,7 @@ constructor( 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 @@ constructor( } true } else { - false + onMove(event) + isDraggingShade() } } ?: false @@ -225,6 +232,32 @@ constructor( } } + /** + * 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 f5c0a94d07f2..334c70b217a3 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 @@ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent = // 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 2fa8f9a1e6fc..fae325cc3147 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 @@ import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity * 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 @@ enum class NoteTaskEntryPoint { /** @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 16dd16ee137e..48a5933a6030 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 @@ package com.android.systemui.notetask 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 @@ class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEvent 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 @@ class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEvent 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 04ed08b6fc20..23ee13b4deac 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 @@ constructor( @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 5355865de093..0641eec154bb 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.annotation.VisibleForTesting 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 @@ import kotlinx.coroutines.flow.asStateFlow /** 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 @@ class FgsManagerControllerImpl @Inject constructor( 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 @@ class FgsManagerControllerImpl @Inject constructor( 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 @@ class FgsManagerControllerImpl @Inject constructor( 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 @@ class FgsManagerControllerImpl @Inject constructor( 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 6be74a0b5646..ce690e239da0 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.R; 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.Arrays; 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 @@ public interface QSHost extends PanelInteractor { 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 958fa71b1fd8..964fe7104324 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.FeatureFlags 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 @@ interface QSHostModule { 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 9f93e4926532..7a10a27f6aca 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 androidx.annotation.VisibleForTesting; 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 @@ public class TileServiceManager { 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 @@ public class TileServiceManager { 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 @@ public class TileServiceManager { @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 @@ public class TileServiceManager { 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 42536fef17aa..121955cced1a 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.broadcast.BroadcastDispatcher; 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 @@ public class TileServices extends IQSService.Stub { 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 @@ public class TileServices extends IQSService.Stub { KeyguardStateController keyguardStateController, CommandQueue commandQueue, StatusBarIconController statusBarIconController, - PanelInteractor panelInteractor) { + PanelInteractor panelInteractor, + CustomTileAddedRepository customTileAddedRepository) { mHost = host; mKeyguardStateController = keyguardStateController; mContext = mHost.getContext(); @@ -101,6 +104,7 @@ public class TileServices extends IQSService.Stub { mStatusBarIconController = statusBarIconController; mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; + mCustomTileAddedRepository = customTileAddedRepository; } public Context getContext() { @@ -128,7 +132,7 @@ public class TileServices extends IQSService.Stub { 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 37a9c40ffacf..bd9d70c13572 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 @@ import kotlinx.coroutines.flow.merge 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 @@ constructor( 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 000000000000..7fc906b04faf --- /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 3090b793552f..4a3199850e0f 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 @@ open class QSTileViewImpl @JvmOverloads constructor( } // 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 1b83397b1afb..f62e93976d71 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_SYS 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.dagger.qualifiers.Main; 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 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @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 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, UserTracker userTracker, ScreenLifecycle screenLifecycle, + WakefulnessLifecycle wakefulnessLifecycle, UiEventLogger uiEventLogger, DisplayTracker displayTracker, KeyguardUnlockAnimationController sysuiUnlockAnimationController, @@ -597,8 +600,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // 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 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - 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()); + 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); + } + } - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurnedOn(); - } else { - Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on 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 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 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); + } + } + }; - /** - * 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()); + 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()); + } - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurningOff(); - } else { - Log.e(TAG_OPS, "Failed to get overview proxy for screen turning off event."); + @Override + public void onFinishedWakingUp() { + mSysUiState + .setFlag(SYSUI_STATE_AWAKE, true) + .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false) + .commitUpdate(mContext.getDisplayId()); } - } 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()); + @Override + public void onStartedGoingToSleep() { + mSysUiState + .setFlag(SYSUI_STATE_AWAKE, false) + .setFlag(SYSUI_STATE_WAKEFULNESS_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."); + @Override + public void onFinishedGoingToSleep() { + mSysUiState + .setFlag(SYSUI_STATE_AWAKE, false) + .setFlag(SYSUI_STATE_WAKEFULNESS_TRANSITION, false) + .commitUpdate(mContext.getDisplayId()); } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e); - } - } - }; + }; 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 a9af1a2457f9..6f85c45a6614 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.ICompatCameraControlCallback; 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 @@ public class ScreenshotController { 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 @@ public class ScreenshotController { 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 f3d2828072be..1cdad83fb0aa 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.ComponentName; 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.RemoteException; 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.internal.util.ScreenshotRequest; 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 @@ public class TakeScreenshotService extends Service { 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 @@ public class TakeScreenshotService extends Service { 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 7b4685216111..b7243ae93cc8 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 @@ package com.android.systemui.shade; 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.pipeline.MediaDataManager; 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 class NotificationPanelViewController implements ShadeSurface, Dump */ 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }; + private final Consumer<Boolean> mMultiShadeExpansionConsumer = + (Boolean expanded) -> mIsAnyMultiShadeExpanded = expanded; + private final Consumer<TransitionStep> mDreamingToLockscreenTransition = (TransitionStep step) -> { mIsOcclusionTransitionRunning = @@ -760,6 +768,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, + Provider<MultiShadeInteractor> multiShadeInteractorProvider, DumpManager dumpManager, KeyguardLongPressViewModel keyguardLongPressViewModel, KeyguardInteractor keyguardInteractor) { @@ -859,7 +868,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } // 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } 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 ad5a68e4dc3f..e08bc33c1ccd 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 @@ public interface ShadeController { 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 826b3ee7a92d..c71467b99961 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 @@ public final class ShadeControllerImpl implements ShadeController { } @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 c435799748ee..fb4feb8c64b4 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.Process; 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 @@ public class CommandQueue extends IStatusBar.Stub implements 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 @@ public class CommandQueue extends IStatusBar.Stub implements } @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 @@ public class CommandQueue extends IStatusBar.Stub implements 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 fda227795915..765c93ed209b 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 @@ public class KeyguardIndicationController { // 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 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { 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 9272f06076fa..0195d4532ae0 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 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn 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 e7760159bff5..8b6617b8f2e1 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 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba * 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 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba // 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 c8174669cc65..b3031515ae9d 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 @@ class ScreenOffAnimationController @Inject constructor( /** * 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 @@ interface ScreenOffAnimation { 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 @@ interface ScreenOffAnimation { 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 118bfc55dd4c..deb041454da4 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 @@ class UnlockedScreenOffAnimationController @Inject constructor( * 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 @@ class UnlockedScreenOffAnimationController @Inject constructor( // 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 @@ class UnlockedScreenOffAnimationController @Inject constructor( } .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 @@ class UnlockedScreenOffAnimationController @Inject constructor( 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 bd60401034b3..e492534b3ff6 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 @@ public final class WMShell implements @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 Binary files differnew file mode 100644 index 000000000000..68473ba6c962 --- /dev/null +++ b/packages/SystemUI/tests/res/drawable-nodpi/romainguy_rockaway.jpg diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 7ce2b1cf38ee..1ba9931e1bc8 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 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { 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 6ae28b73b348..a8d5569a1b98 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 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { } @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 33f0ae5563f7..b62875988b2e 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 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { } @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 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { } @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 bb03764268a6..b2c2ae7458ae 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_BP 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.testing.TestableLooper.RunWithLooper 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.SysuiTestCase 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 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { 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 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { 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 445cc8739463..edee3f1b9f02 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 @@ public class UdfpsControllerTest extends SysuiTestCase { } @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 f437a8f009f8..6d9acb92a5f4 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 @@ package com.android.systemui.biometrics; 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 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController } @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 236384b09514..4ea96169e8a5 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.Test 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 @@ class AllModelTest : SysuiTestCase() { } verify(controlsModelCallback).onFirstChange() + verify(controlsModelCallback).onChange() } @Test @@ -176,6 +178,7 @@ class AllModelTest : SysuiTestCase() { ) verify(controlsModelCallback).onFirstChange() + verify(controlsModelCallback).onChange() } @Test @@ -191,6 +194,7 @@ class AllModelTest : SysuiTestCase() { } verify(controlsModelCallback, never()).onFirstChange() + verify(controlsModelCallback, never()).onChange() } @Test @@ -207,6 +211,7 @@ class AllModelTest : SysuiTestCase() { } verify(controlsModelCallback).onFirstChange() + verify(controlsModelCallback).onChange() } @Test @@ -222,6 +227,7 @@ class AllModelTest : SysuiTestCase() { ) verify(controlsModelCallback).onFirstChange() + verify(controlsModelCallback).onChange() } @Test @@ -236,5 +242,24 @@ class AllModelTest : SysuiTestCase() { } 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 3b6f7d19e93e..42106756b473 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 @@ package com.android.systemui.controls.management 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 @@ import org.mockito.MockitoAnnotations @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 @@ class ControlsEditingActivityTest : SysuiTestCase() { @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 @@ class ControlsEditingActivityTest : SysuiTestCase() { ) { override fun create(intent: Intent?): TestableControlsEditingActivity { return TestableControlsEditingActivity( + featureFlags, uiExecutor, controller, userTracker, customIconCache, - uiController, mockDispatcher, latch ) @@ -75,19 +86,17 @@ class ControlsEditingActivityTest : SysuiTestCase() { @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 @@ class ControlsEditingActivityTest : SysuiTestCase() { 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 @@ class ControlsEditingActivityTest : SysuiTestCase() { // 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 365523233353..68846168d17b 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 @@ import org.mockito.MockitoAnnotations @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 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @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 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { ) { override fun create(intent: Intent?): TestableControlsFavoritingActivity { return TestableControlsFavoritingActivity( + featureFlags, executor, controller, listingController, userTracker, - uiController, mockDispatcher, latch ) @@ -75,19 +108,18 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @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 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { 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 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { // 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 000000000000..977e3ba899f6 --- /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 6b3ec682b12b..a7d7b93c4044 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 androidx.test.filters.SmallTest; 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 @@ public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase { when(params.getDisplayStateSupported()).thenReturn(false); assertEquals(DozeScreenStatePreventingAdapter.class, - DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass()); + DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor) + .getClass()); } @Test @@ -94,6 +104,7 @@ public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase { 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 9ae7217d317e..240d2d7bd35c 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 androidx.test.filters.SmallTest; 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 @@ public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase { when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false); assertEquals(DozeSuspendScreenStatePreventingAdapter.class, - DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass()); + DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor) + .getClass()); } @Test @@ -100,6 +107,7 @@ public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase { 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 7f6e2ba1c0f9..08427dab978b 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 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { } @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,13 +431,58 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { 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); @@ -476,7 +535,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { 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 000000000000..5704ef3f37db --- /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 000000000000..ccd631ec37d0 --- /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 fb7d379c0627..5d83f561fdc2 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 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 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 000000000000..6e002f5a9a9a --- /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 70f766f719e9..e57b04495e40 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.ExperimentalCoroutinesApi 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 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { ) } - @After - fun tearDown() { - // verify(keyguardUpdateMonitor).removeCallback(updateMonitorCallback.value) - } - @Test fun isLockedOut_whenFingerprintLockoutStateChanges_emitsNewValue() = testScope.runTest { @@ -129,29 +124,55 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { } @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 d55370b20d09..000000000000 --- 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 7f3016270def..68d694aaf20f 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 kotlinx.coroutines.test.runTest 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 @@ class KeyguardInteractorTest : SysuiTestCase() { 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 @@ class KeyguardInteractorTest : SysuiTestCase() { 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 aa54a1cdf579..447b333b942f 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 junit.framework.Assert.assertNull; 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 android.testing.TestableLooper; 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 @@ public class SessionTrackerTest extends SysuiTestCase { private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private KeyguardStateController mKeyguardStateController; + @Mock + private UiEventLogger mUiEventLogger; @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; @@ -87,7 +91,8 @@ public class SessionTrackerTest extends SysuiTestCase { mStatusBarService, mAuthController, mKeyguardUpdateMonitor, - mKeyguardStateController + mKeyguardStateController, + mUiEventLogger ); } @@ -238,6 +243,62 @@ public class SessionTrackerTest extends SysuiTestCase { 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 acde887818aa..19f9960558b2 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 androidx.test.filters.SmallTest 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.remoteproxy.MultiShadeInputProxy 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.Before 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 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { 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 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { 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 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { } @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 fbe089a0616f..ba29ca57cefb 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 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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 cd67e8d0a4c2..ec4daee72cf8 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 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { // 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 6f54f62cd70c..f5a3becc7017 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 @@ public class FgsManagerControllerTest extends SysuiTestCase { 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 46af89e00db4..9ca7a8521e95 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 androidx.test.runner.AndroidJUnit4; 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 @@ public class TileServiceManagerTest extends SysuiTestCase { private QSHost mQSHost; @Mock private Context mMockContext; + @Mock + private CustomTileAddedRepository mCustomTileAddedRepository; private HandlerThread mThread; private Handler mHandler; @@ -86,8 +89,9 @@ public class TileServiceManagerTest extends SysuiTestCase { 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 @@ public class TileServiceManagerTest extends SysuiTestCase { @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 fb9336734d99..12b5656725eb 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 android.testing.TestableLooper.RunWithLooper; 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 @@ public class TileServicesTest extends SysuiTestCase { private PanelInteractor mPanelInteractor; @Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksArgumentCaptor; + @Mock + private CustomTileAddedRepository mCustomTileAddedRepository; @Before public void setUp() throws Exception { @@ -115,7 +118,7 @@ public class TileServicesTest extends SysuiTestCase { mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher, mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController, - mPanelInteractor); + mPanelInteractor, mCustomTileAddedRepository); } @After @@ -293,9 +296,11 @@ public class TileServicesTest extends SysuiTestCase { 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 0b9fbd919715..59f0d967596b 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 @@ class FooterActionsViewModelTest : SysuiTestCase() { 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 000000000000..d7ab903c1e2e --- /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 d3ec1dd8bfb2..28aeba461c50 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 @@ class QSTileViewImplTest : SysuiTestCase() { } @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 eb7b481e5244..8cb5d31fff22 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 @@ package com.android.systemui.recents 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.dagger.qualifiers.Main 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.recents.OverviewProxyService.ACTION_QUICKSTEP 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.ArgumentMatchers 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 @@ class OverviewProxyServiceTest : SysuiTestCase() { 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 @@ class OverviewProxyServiceTest : SysuiTestCase() { sysUiState, userTracker, screenLifecycle, + wakefulnessLifecycle, uiEventLogger, displayTracker, sysuiUnlockAnimationController, @@ -145,42 +153,48 @@ class OverviewProxyServiceTest : SysuiTestCase() { } @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 47d88a50695f..77f742647497 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.content.ComponentName 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.times 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 @@ class TakeScreenshotServiceTest : SysuiTestCase() { .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 @@ class TakeScreenshotServiceTest : SysuiTestCase() { 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 @@ class TakeScreenshotServiceTest : SysuiTestCase() { } @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,100 +208,6 @@ class TakeScreenshotServiceTest : SysuiTestCase() { @Test fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() { - flags.set(SCREENSHOT_METADATA_REFACTOR, true) - - 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 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) @@ -442,51 +256,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { } @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 1bd13aadc1f9..7b37ea0c9a1a 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.pipeline.MediaDataManager; 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 2a108239bac5..bc8ab1faf9eb 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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { 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 86660a462c59..56385b2ec5da 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 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { 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 374aae1f2948..78f5bf20e6f9 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 @@ class ClockRegistryTest : SysuiTestCase() { isEnabled = true, handleAllUsers = true, defaultClockProvider = fakeDefaultProvider, - keepAllLoaded = true, + keepAllLoaded = false, subTag = "Test", ) { override fun querySettings() { } @@ -154,8 +154,8 @@ class ClockRegistryTest : SysuiTestCase() { 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 @@ class ClockRegistryTest : SysuiTestCase() { 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 @@ class ClockRegistryTest : SysuiTestCase() { 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 f581154f66c0..f4cd383f7c4c 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.IBiometricSysuiReceiver; 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 @@ public class CommandQueueTest extends SysuiTestCase { @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 39ea46a657f2..e2aef31b4f10 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 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr } 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 d8b3270d3aff..65735f028c41 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.Flow 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 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { 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 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { _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 000000000000..fe941179830a --- /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 00b1a401ac79..4bfd3d64c98e 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 @@ class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepos 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 1dda47223dd6..8a6d2aa7dd21 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 @@ class FakeKeyguardBouncerRepository : KeyguardBouncerRepository { 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 194ed02712b2..d4115900850f 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 @@ class FakeKeyguardRepository : KeyguardRepository { _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 000000000000..6690de87d0a2 --- /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 ced7955100f7..9ff7dd590781 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 @@ import kotlinx.coroutines.flow.MutableStateFlow /** 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 53bb340fc167..fbc2381c3031 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 @@ class FakeUserRepository : UserRepository { 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 e159f18809c4..7463061256b7 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ */ 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ 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 8473ab7f6288..ae4e531f9b72 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_PACKAGE_BROA 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager, - SystemActionPerformer.SystemActionsChangedListener { + SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{ private static final boolean DEBUG = false; @@ -471,7 +474,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }; 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mUiAutomationManager.getServiceInfo(), client) ? mUiAutomationManager.getRelevantEventTypes() : 0; - relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked(); return relevantEventTypes; } @@ -2130,7 +2181,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private static boolean isClientInPackageAllowlist( + static boolean isClientInPackageAllowlist( @Nullable AccessibilityServiceInfo serviceInfo, Client client) { if (serviceInfo == null) return false; @@ -2323,24 +2374,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } inputFilter = mInputFilter; setInputFilter = true; - mProxyManager.setAccessibilityInputFilter(mInputFilter); } mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags); mInputFilter.setCombinedGenericMotionEventSources( @@ -2491,6 +2548,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub "inputFilter=" + inputFilter); } mWindowManagerService.setInputFilter(inputFilter); + mProxyManager.setAccessibilityInputFilter(inputFilter); } } @@ -2555,6 +2613,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * @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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } boolean isDisplayProxyed(int displayId) { - return mProxyManager.isProxyed(displayId); + return mProxyManager.isProxyedDisplay(displayId); } @Override @@ -4009,13 +4099,71 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 b19a502547ab..ab01fc324ed2 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 @@ import java.util.Set; 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 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon /** The color of the focus rectangle */ private int mFocusColor; + private int mInteractiveTimeout; + private int mNonInteractiveTimeout; + ProxyAccessibilityServiceConnection( Context context, ComponentName componentName, @@ -83,7 +87,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon 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 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon 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 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon 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 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon // Update connection with mAccessibilityServiceInfo values. setDynamicallyConfigurableProperties(proxyInfo); // Notify manager service. - mSystemSupport.onClientChangeLocked(true); + mSystemSupport.onProxyChanged(mDeviceId); } } finally { Binder.restoreCallingIdentity(identity); @@ -235,12 +249,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon 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 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon 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 e258de16caf5..d417197e1419 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 @@ public class ProxyManager { 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; + + private final UiAutomationManager mUiAutomationManager; - // 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; + // Device Id -> state. Used to determine if we should notify AccessibilityManager clients of + // updates. + private final SparseIntArray mLastStates = new SparseIntArray(); - private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections = + // 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 @@ public class ProxyManager { 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 @@ public class ProxyManager { 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 @@ public class ProxyManager { @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 @@ public class ProxyManager { * Unregister the proxy based on display id. */ public boolean unregisterProxy(int displayId) { - return clearConnection(displayId); + return clearConnectionAndUpdateState(displayId); + } + + /** + * Clears all proxy connections belonging to {@code deviceId}. + */ + 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)); + } } - private boolean clearConnection(int displayId) { - boolean removed = false; + /** + * 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); - removed = true; - if (DEBUG) { - Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId); - } + removedFromConnections = true; } } - 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); - } + + 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); } - return removed; } /** - * Checks if a display id is being proxy-ed. + * Returns {@code true} if {@code displayId} is being proxy-ed. */ - public boolean isProxyed(int displayId) { + public boolean isProxyedDisplay(int displayId) { synchronized (mLock) { final boolean tracked = mProxyA11yServiceConnections.contains(displayId); if (DEBUG) { @@ -176,6 +306,23 @@ public class ProxyManager { } /** + * 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 @@ public class ProxyManager { /** * 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 @@ public class ProxyManager { != 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(int deviceId) { + return mLastStates.get(deviceId, 0); + } + + /** + * Sets the last state for a device. */ - public int getLastSentStateLocked() { - return mLastState; + public void setLastStateLocked(int deviceId, int proxyState) { + mLastStates.put(deviceId, proxyState); } /** - * Sets the last state. + * 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 void setLastStateLocked(int proxyState) { - mLastState = proxyState; + 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 of every proxy. - * TODO(254545943): When A11yManager is separated, return based on the A11yManager display. + * Returns the relevant event types for a Client. */ - public int getRelevantEventTypesLocked() { + 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 + * Adds the service interfaces to a list. + * @param interfaces the list to add to. + * @param deviceId the device id of the interested app client. */ - public int getNumProxysLocked() { - return mProxyA11yServiceConnections.size(); + public void addServiceInterfacesLocked(@NonNull List<IAccessibilityServiceClient> interfaces, + int deviceId) { + for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) { + final ProxyAccessibilityServiceConnection proxy = + mProxyA11yServiceConnections.valueAt(i); + if (proxy != null && proxy.getDeviceId() == deviceId) { + final IBinder proxyBinder = proxy.mService; + final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface; + if ((proxyBinder != null) && (proxyInterface != null)) { + interfaces.add(proxyInterface); + } + } + } } /** - * Adds the service interfaces to a list. - * @param interfaces + * 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 addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) { + 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); - 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 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 @@ public class ProxyManager { } 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 @@ public class ProxyManager { 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()); + + } + } + } + + 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"); + } } } } -}
\ No newline at end of file +} 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 f3a949d29ff8..dd7d38f34f28 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 @@ public class SystemDataTransferProcessor { @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 b338d89a0169..1363ef31c68d 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub */ 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 8fe61e719817..78cbf2bd80d8 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 @@ public final class ActiveServices { 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 82c4796a21ef..553706db34d8 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 9079ba84c8f3..5dd0a3f58217 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 @@ class ActivityManagerDebugConfig { // 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 74676cf96ef8..06e6df94575e 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_OOM_ADJ; 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 @@ public class ActivityManagerService extends IActivityManager.Stub || 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 @@ public class ActivityManagerService extends IActivityManager.Stub + "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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub * 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 72e17d8764aa..350ac3bd4360 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_CRI 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.ArrayMap; 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 @@ final class ActivityManagerShellCommand extends ShellCommand { } 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 @@ final class ActivityManagerShellCommand extends ShellCommand { } 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 d9ba8453fffa..ed297d0867a1 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub synchronized (mLock) { final long elapsedRealtime = SystemClock.elapsedRealtime(); mHandler.post(() -> { + mCpuWakeupStats.onUidRemoved(uid); synchronized (mStats) { mStats.removeUidStatsLocked(uid, elapsedRealtime); } @@ -764,6 +765,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 a5535cb13165..92547eab1467 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 @@ public class BroadcastLoopers { * 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 @@ public class BroadcastLoopers { * 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 @@ public class BroadcastLoopers { /** * 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 @@ public class BroadcastLoopers { 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 056e17a5ef3c..48ab96cee562 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 @@ class BroadcastProcessQueue { 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 @@ class BroadcastProcessQueue { REASON_CONTAINS_RESULT_TO, REASON_CONTAINS_INSTRUMENTED, REASON_CONTAINS_MANIFEST, + REASON_FOREGROUND_ACTIVITIES, }) @Retention(RetentionPolicy.SOURCE) public @interface Reason {} @@ -906,6 +908,7 @@ class BroadcastProcessQueue { 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 @@ class BroadcastProcessQueue { } 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 75e93366bce4..6d1344d79b6c 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 @@ public abstract class BroadcastQueue { 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 @@ public abstract class BroadcastQueue { * 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 @@ public abstract class BroadcastQueue { * 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 4b8dc99c67d9..bd36c3ff6f98 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 @@ public class BroadcastQueueImpl extends BroadcastQueue { 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 @@ public class BroadcastQueueImpl extends BroadcastQueue { } 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 7157fff53ccf..a1fccbc17a3a 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.BROADCAST_DELIVERY_EVE 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 6c9f602bbc94..9c1546324e4b 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 @@ public final class CachedAppOptimizer { // 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { @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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { } } - // 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { @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 (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); + } } + } - 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); + /** + * Callback received after a process has been frozen. + */ + void onProcessFrozen(ProcessRecord frozenProc) { + if (useCompaction()) { + synchronized (mProcLock) { + compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false); } - } 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); } } @@ -1687,26 +1638,6 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { } } } + if (proc.mOptRecord.isFrozen()) { + onProcessFrozen(proc); + } } break; case REPORT_UNFREEZE_MSG: @@ -2050,6 +1984,10 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 @@ public final class CachedAppOptimizer { 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 84a80993c4b2..a98571b68067 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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { 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 7788ea44571e..8fce88809010 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.Watchdog.NATIVE_STACKS_OF_INTEREST; 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.os.anr.AnrLatencyTracker; 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 @@ class ProcessErrorStateRecord { }); } } + // 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 @@ class ProcessErrorStateRecord { 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 @@ class ProcessErrorStateRecord { 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 50d00b471f65..e651e23a5318 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 @@ class ProcessRecord implements WindowProcessListener { 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 2e99e97ee776..d9553a37f716 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 @@ public class StackTracesDumpHelper { 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 @@ public class StackTracesDumpHelper { AnrLatencyTracker latencyTracker) { return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, logExceptionCreatingFile, null, subject, criticalEventSection, - auxiliaryTaskExecutor, null, latencyTracker); + /* memoryHeaders= */ null, auxiliaryTaskExecutor, null, latencyTracker); } /** @@ -119,8 +119,8 @@ public class StackTracesDumpHelper { 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 @@ public class StackTracesDumpHelper { 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 000000000000..bc59dd6456fa --- /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 000000000000..d81a13fcfa90 --- /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 cb2c54349c6d..886add3a3c8e 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 @@ package com.android.server.appop; 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.PackageManagerInternal; 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 android.util.Xml; 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 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface @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 class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface 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 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface } @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 018db17b18a9..0be69ce7b861 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 @@ public class AppOpsService extends IAppOpsService.Stub { } 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 000000000000..a6d505021090 --- /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 000000000000..f0e4b0f59b06 --- /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 b440208e3e32..24332112ed76 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context); + mHardwareKeyboardShortcutController.reset(mSettings); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null @@ -3268,6 +3271,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @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 efa80b7a0939..34b8e65b676e 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 @@ public class LocationManagerService extends ILocationManager.Stub implements } 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 cb952edd0b71..87e193f89571 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.lang.Math.min; 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 @@ public class LocationEventLog extends LocalEventLog<Object> { 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 @@ public class LocationEventLog extends LocalEventLog<Object> { } } + /** 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 @@ public class LocationEventLog extends LocalEventLog<Object> { 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 @@ public class LocationEventLog extends LocalEventLog<Object> { } } + 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 @@ public class LocationEventLog extends LocalEventLog<Object> { 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 @@ public class LocationEventLog extends LocalEventLog<Object> { } } } + + /** + * 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 041f11d972fe..d02b6f4cff53 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 @@ package com.android.server.location.gnss; 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 @@ public final class GnssMeasurementsProvider extends @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 @@ public final class GnssMeasurementsProvider extends 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 71f8e1622122..46c259479d08 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { // 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 e6fd7ec06cdc..b6fd822b7687 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 @@ public final class PermissionHelper { } /** + * 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 20e5d3900941..7603c45782fc 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { private final Handler mHandler; private final File mUsersDir; - @GuardedBy("mPackagesLock") private final File mUserListFile; private final IBinder mUserRestrictionToken = new Binder(); @@ -3640,77 +3640,95 @@ public class UserManagerService extends IUserManager.Stub { 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; + 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; + } } } - } - } 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); + } 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; } - 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 @@ public class UserManagerService extends IUserManager.Stub { } } + 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 @@ public class UserManagerService extends IUserManager.Stub { } } + 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 @@ public class UserManagerService extends IUserManager.Stub { 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); + } + 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); } - } - 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ public class UserManagerService extends IUserManager.Stub { + ", 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 f340f9374dd5..c9ebeaee88ce 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 @@ final class VerifyingSession { 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 572e13c579b4..df0192c71df2 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.IPermissionManager; 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 a2132682b3fc..2ae39b732b12 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 @@ public class PermissionMigrationHelperImpl implements PermissionMigrationHelper 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 449d2eb7b65b..adaad2aef02d 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { @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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mAccessibilityManager.isEnabled() || !mAccessibilityManager.sendFingerprintGesture(event.getKeyCode())) { if (mSystemNavigationKeysEnabled) { - sendSystemKeyToStatusBarAsync(event.getKeyCode()); + sendSystemKeyToStatusBarAsync(event); } } } @@ -4538,11 +4545,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** * 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** * 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 a9a1d5e9102c..1a22b8904579 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 com.android.server.utils.Slogf; 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 @@ public final class HintManagerService extends SystemService { 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 @@ public final class HintManagerService extends SystemService { 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 d55fbc27e109..231ffc6464c1 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_ALARM; 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.Trace; 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { @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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { /** 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { 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 @@ public class CpuWakeupStats { } } - 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 @@ public class CpuWakeupStats { 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 fdc28224a692..8da9e1d628b5 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.Keystore2AtomWithOverflow; 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { registerSettingsStats(); registerInstalledIncrementalPackages(); registerKeystoreStorageStats(); - registerRkpPoolStats(); registerKeystoreKeyCreationWithGeneralInfo(); registerKeystoreKeyCreationWithAuthInfo(); registerKeystoreKeyCreationWithPurposeModesInfo(); @@ -4316,14 +4313,6 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 35e88c1a2485..363d2fdf7f4c 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.IndentingPrintWriter; 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @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 94e041aa3937..5f56923e2f53 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.PROC_ID; 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 c9213d5c0e9b..d2fc3931c653 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.MAINLINE_LAST_OR 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 22dd0e5128e9..d31fe23bf2be 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 @@ public class DisplayPolicy { 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 052c09a0e0eb..d65f464590c1 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 @@ class EmbeddedWindowController { } } - 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 @@ class EmbeddedWindowController { 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 @@ class EmbeddedWindowController { */ 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 @@ class EmbeddedWindowController { final String hostWindowName = (mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString() : ""; + mIsFocusable = isFocusable; mName = "Embedded{" + inputHandleName + hostWindowName + "}"; } @@ -279,13 +265,6 @@ class EmbeddedWindowController { return mOwnerUid; } - void setIsOverlay() { - mIsOverlay = true; - } - boolean getIsOverlay() { - return mIsOverlay; - } - IBinder getFocusGrantToken() { return mFocusGrantToken; } @@ -297,20 +276,33 @@ class EmbeddedWindowController { 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 4be98a3c88b7..b4dffdcba243 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 @@ import java.io.PrintWriter; * 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 301c1846249f..3d4e0ebac258 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 @@ class InputWindowHandleWrapper { 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 a8c9cd30b656..fe13b87a079a 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 @@ class InsetsPolicy { 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 @@ class InsetsPolicy { } } - 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 0953604511d7..3b23f9717175 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 @@ import java.util.function.Consumer; * 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 fca333d33731..249ead0a8509 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 @@ class InsetsStateController { 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 @@ class InsetsStateController { 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 @@ class InsetsStateController { } 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 b7e2265e3a16..db4453297001 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 @@ class Task extends TaskFragment { 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 c6ba60026cdf..0fe1f923e4e5 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_DISPLAY; 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + /** 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } - 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { || (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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { && (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 57c0d65b28ea..a4c931c17a66 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 @@ public class TransitionTracer { 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 88c410b263ca..f8edc2b871be 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 @@ class TrustedOverlayHost { 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 2e217820c264..41176410a789 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 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } 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 aa2e8f541058..000000000000 --- 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 d7c9d9c71138..2156c6dfae33 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 437af4b4ac76..a153708c98d5 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 @@ public class WindowManagerShellCommand extends ShellCommand { 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 71acbb439c88..d64b5a17b33d 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 @@ static struct { jmethodID getContextForDisplay; jmethodID notifyDropWindow; jmethodID getParentSurfaceForPointers; - jmethodID isPerDisplayTouchModeEnabled; } gServiceClassInfo; static struct { @@ -369,10 +368,6 @@ public: 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 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { 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 @@ int register_android_server_InputManager(JNIEnv* env) { 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 c7a1af77389d..b21489abe2a9 100644 --- a/services/core/jni/gnss/AGnssRil.cpp +++ b/services/core/jni/gnss/AGnssRil.cpp @@ -89,6 +89,10 @@ jboolean AGnssRil::updateNetworkState(jboolean connected, jint type, jboolean ro } 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 f01b0eb08712..7d26c0400d8d 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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(); + affectedUserId); + if (which == 0) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.KEYGUARD_DISABLED_FEATURES, admin, affectedUserId); + } else { + // 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); + } + invalidateBinderCaches(); } else { - ap = getActiveAdminForCallerLocked( + 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; + 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 & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; + which = which & PROFILE_KEYGUARD_FEATURES; } - } else { - which = which & PROFILE_KEYGUARD_FEATURES; } - } - if (ap.disabledKeyguardFeatures != which) { - ap.disabledKeyguardFeatures = which; - saveSettingsLocked(userHandle); + if (ap.disabledKeyguardFeatures != which) { + ap.disabledKeyguardFeatures = which; + saveSettingsLocked(userHandle); + } } } if (SecurityLog.isLoggingEnabled()) { @@ -9252,15 +9271,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } return which; + } finally { + mInjector.binderRestoreCallingIdentity(ident); } - } finally { - mInjector.binderRestoreCallingIdentity(ident); } } @@ -12519,9 +12574,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 a08c20548fbd..8812c3d3c557 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 @@ final class PolicyDefinition<V> { (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 c7f52239efe9..dbff90a1aee3 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -108,6 +108,8 @@ import com.android.internal.widget.LockSettingsInternal; 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 @@ public final class SystemServer implements Dumpable { 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 000000000000..71e4f2a9c65b --- /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 5a2522e0d53d..bd949550ed04 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 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { } } + 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 000000000000..4bd36f4a9cdb --- /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 000000000000..c9651b217f6f --- /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 7d3578de2fec..b4c4796bbc97 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 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { } } + 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 000000000000..fdf2b64257f9 --- /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 ee7a4f47a109..b4b185ffb800 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 @@ object PackageVersionMigration { 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 @@ object PackageVersionMigration { 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 111cabd298f5..000000000000 --- 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 d8139623f393..a849b669ac3a 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.android.tradefed.testtype.junit4.BaseHostJUnit4Test 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 @@ import java.util.regex.Pattern @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 36d191b466ba..41a5ddb60a7f 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.dx.mockito.inline.extended.ExtendedMockito.verify; 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 @@ public final class BroadcastQueueModernImplTest { 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 bca39ae64823..3b964bcb78ac 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 @@ package com.android.server.am; 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 @@ public class BroadcastQueueTest { 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 @@ public class BroadcastQueueTest { } 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 @@ public class BroadcastQueueTest { 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 @@ public class BroadcastQueueTest { // 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 @@ public class BroadcastQueueTest { 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 @@ public class BroadcastQueueTest { 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 1fbb8dd5ac9d..eb6efd2e859f 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 @@ public final class CachedAppOptimizerTest { 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 b5e0e0730a58..dd44a7968639 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 @@ import java.util.List; 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 @@ public class ProxyAccessibilityServiceConnectionTest { 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 @@ public class ProxyAccessibilityServiceConnectionTest { 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 000000000000..6eedeea8c333 --- /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 397d7b5f2a3e..7cf5bc88c213 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class CpuWakeupStatsTest { 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 @@ public class CpuWakeupStatsTest { 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 @@ public class CpuWakeupStatsTest { // 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 @@ public class CpuWakeupStatsTest { 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 @@ public class CpuWakeupStatsTest { // 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 @@ public class CpuWakeupStatsTest { 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 @@ public class CpuWakeupStatsTest { 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 @@ public class CpuWakeupStatsTest { 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 @@ public class CpuWakeupStatsTest { // 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 42d1ace37ba5..3888b9b14c15 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, + /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true, /* isSticky= */ true); } @@ -10527,7 +10501,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE, - /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, + /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true, /* isSticky= */ true); } @@ -10536,7 +10510,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 f2b1dc9132d5..397e3c1f55a2 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 @@ public class PermissionHelperTest extends UiServiceTestCase { } @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 000000000000..ff2ce15a7946 --- /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 index f49d9c9f1431..e6a1be8f018d 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.KeyphraseRecognitionEvent; 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 @@ public class SoundTriggerTest extends InstrumentationTestCase { 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 5661b1288345..7b7a0a3f0285 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 @@ public class ConversionUtilTest { 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 20d410cce370..2914de115eff 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 @@ public class DisplayPolicyTests extends WindowTestsBase { 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 @@ public class DisplayPolicyTests extends WindowTestsBase { 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 index ef20f2b8fe64..b35eceb6dd11 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 @@ import org.junit.runner.RunWith; @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 74fde65c4dcd..ff2944a80976 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 @@ public class InsetsStateControllerTest extends WindowTestsBase { // 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 class InsetsStateControllerTest extends WindowTestsBase { 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 d7bf4b0e02b2..90506d4f8651 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 @@ public class TransitionTests extends WindowTestsBase { 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 6261e56a87c5..a1ddd5748002 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 @@ public class WindowContainerTests extends WindowTestsBase { 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 0d7cdc851ddf..7e3ec55f262a 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 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 86c4bbfe56b8..37a325e09e56 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 @@ public class FakeSoundTriggerHal extends ISoundTriggerHw.Stub { // 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 @@ public class FakeSoundTriggerHal extends ISoundTriggerHw.Stub { } } - /** - * 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 @@ public class FakeSoundTriggerHal extends ISoundTriggerHw.Stub { } } } + + // 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 @@ public class FakeSoundTriggerHal extends ISoundTriggerHw.Stub { 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 2f8d17d77e52..4c134af18552 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.BatteryStatsInternal; 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 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt printObject(originatorIdentity), printArgs(args), printObject(retVal)); - Log.i(TAG, message); + Slog.i(TAG, message); appendMessage(message); } @@ -474,7 +474,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt object, printObject(originatorIdentity), printArgs(args)); - Log.i(TAG, message); + Slog.i(TAG, message); appendMessage(message); } @@ -486,7 +486,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt 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 fa4c9b23ae78..ff378ba02856 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.CompletionException; 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 @@ public class ImsRegistrationImplBase { } 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 @@ public class ImsRegistrationImplBase { @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 @@ public class ImsRegistrationImplBase { @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 @@ public class ImsRegistrationImplBase { 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 @@ public class ImsRegistrationImplBase { 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 class ImsRegistrationImplBase { 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 @@ public class ImsRegistrationImplBase { 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 2954c2d5a6d7..e229f05b1ad3 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl @@ -18,7 +18,7 @@ package android.telephony.satellite; 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 @@ oneway interface ISatelliteDatagramCallback { * @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 d8a6fafcdd2f..d0409bf5df49 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -19,7 +19,7 @@ package android.telephony.satellite; 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 @@ public interface SatelliteDatagramCallback { * @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 7d82fd8d6d69..20f9bc8bef05 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -37,7 +37,7 @@ import android.telephony.SubscriptionManager; 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 @@ public class SatelliteManager { * * @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 @@ public class SatelliteManager { */ @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 @@ public class SatelliteManager { () -> 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 @@ public class SatelliteManager { @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 @@ public class SatelliteManager { * 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 a780cb936ebd..ea4e2e2ef1b9 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -67,6 +67,15 @@ oneway interface ISatellite { 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 @@ oneway interface ISatellite { * * @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 @@ oneway interface ISatellite { * 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 debb394ed234..17d026cc8c1c 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -79,6 +79,15 @@ public class SatelliteImplBase extends SatelliteService { } @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 @@ public class SatelliteImplBase extends SatelliteService { } @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 @@ public class SatelliteImplBase extends SatelliteService { } /** + * 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 @@ public class SatelliteImplBase extends SatelliteService { * * @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 @@ public class SatelliteImplBase extends SatelliteService { * 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 b8900052caf6..cbdf38ae95d4 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2823,15 +2823,15 @@ interface ITelephony { * @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 000000000000..b5557fde2a02 --- /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 fef521163399..4ba538ed9d45 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -50,6 +50,9 @@ android_test { "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 9dc4bf034e66..314b9e4a853b 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 @@ fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: ICompon * * @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 7aea05d0ce9b..fde098199042 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 @@ constructor( * 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 79c048a14e84..d4f48fe8d50f 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 @@ constructor( * * @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 @@ constructor( * @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 e497ae4779a7..a72c12dcb463 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 @@ open class PipAppHelper(instrumentation: Instrumentation) : 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 @@ open class PipAppHelper(instrumentation: Instrumentation) : // 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 @@ open class PipAppHelper(instrumentation: Instrumentation) : 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 @@ open class PipAppHelper(instrumentation: Instrumentation) : 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 @@ open class PipAppHelper(instrumentation: Instrumentation) : 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 432df209ed39..c355e2708657 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 @@ class CloseImeOnDismissPopupDialogTestCfArm(flicker: FlickerTest) : ) } } -}
\ 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 a4e4b6f40867..df9d33bf2c8f 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 @@ import org.junit.runners.Parameterized * 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 e85da1f09772..7954dd13e906 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 @@ import org.junit.runners.Parameterized * 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 e2d6dbf428f9..2fff00133eb8 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.FlakyTest 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 androidx.test.filters.RequiresDevice 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 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f @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 1fee20d7803a..a3fb73bad25f 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 @@ open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: Fl @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 efda0fffd3cd..e1aa4182c331 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 @@ class ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm(flicker: FlickerTes ) } } -}
\ 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 daee3322b15a..690ed53df5f2 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.platform.test.annotations.Presubmit 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 @@ open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: Flicker } } /** {@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 7514c9befe4f..866e858f3865 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 @@ package com.android.server.wm.flicker.ime 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 @@ import org.junit.runners.Parameterized * 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 a57aa5bc745c..6f225891f39c 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 @@ package com.android.server.wm.flicker.ime 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.navigationBars 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 cffc05d7d0b7..8891d26c9e54 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 @@ class ShowImeWhileDismissingThemedPopupDialogTestCfArm(flicker: FlickerTest) : ) } } -}
\ 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 9ea12a9e22a0..231d0d74c2a1 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 @@ package com.android.server.wm.flicker.ime 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.flicker.legacy.FlickerTestFactory 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 @@ open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(fli 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 @@ open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(fli 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 @@ open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(fli @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 e8f9aa3038ef..3c577ac2db35 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 @@ import org.junit.runners.Parameterized * 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 05abf9fd1a8e..360a2336962c 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 @@ import org.junit.runners.Parameterized * 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 63ffee6fd77b..12c08748cbac 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 @@ open class OpenAppFromLockNotificationCold(flicker: FlickerTest) : } /** - * 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 a221ef6963c3..222caed31b0c 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 @@ class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotific } /** - * 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 d90b3ca75636..43d28fa60e51 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 @@ class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) : 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 3fccd12af1c4..6fa65fd940ec 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.content.res.Resources 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 androidx.test.filters.RequiresDevice 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 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) { /** 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 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) { .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 63299cb6cd7a..d49f035fa84a 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.NavBar 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 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.SimpleAppHelper -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test @@ -262,17 +260,9 @@ open class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) @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 4a4180b6bbff..fe789a7a7384 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 @@ import org.junit.runners.Parameterized * 0 -> 90 degrees * 90 -> 0 degrees * ``` + * * Actions: * ``` * Launch an app (via intent) @@ -47,22 +48,29 @@ import org.junit.runners.Parameterized * 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 17b3b2b97e4b..4d010f35d4eb 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 @@ import org.junit.runners.Parameterized * 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 @@ import org.junit.runners.Parameterized * 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 de86498afd27..5f03aa27e6df 100644 --- a/tests/OdmApps/Android.bp +++ b/tests/OdmApps/Android.bp @@ -26,4 +26,7 @@ java_test_host { 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 cc0fa1cd0840..000000000000 --- 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 f7454c752b7d..000000000000 --- 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 1e41886fe716..000000000000 --- 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 2c3592c640bc..000000000000 --- 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 ffde8c7d342c..23efe548c82a 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -55,6 +55,7 @@ java_test_host { "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 79744625b752..cc8bc45a7411 100644 --- a/tests/SystemMemoryTest/host/Android.bp +++ b/tests/SystemMemoryTest/host/Android.bp @@ -26,4 +26,7 @@ java_test_host { srcs: ["src/**/*.java"], libs: ["tradefed"], test_suites: ["general-tests"], + data: [ + ":SystemMemoryTestDevice", + ], } |